PageSourceSearch

https://circuitoftheamericas.com/wp-content/themes/cota/assets/js/ScrollMagic.js

js circuitoftheamericas.com collected 2026-09-24 09:14:30 UTC 108,938 bytes, 2,816 lines download raw bytes

1/*!
2 * ScrollMagic v2.0.7 (2019-05-07)
3 * The javascript library for magical scroll interactions.
4 * (c) 2019 Jan Paepke (@janpaepke)
5 * Project Website: http://scrollmagic.io
6 * 
7 * @version 2.0.7
8 * @license Dual licensed under MIT license and GPL.
9 * @author Jan Paepke - [email protected]
10 *
11 * @file ScrollMagic main library.
12 */
13/**
14 * @namespace ScrollMagic
15 */
16(function (root, factory) {
17	if (typeof define === 'function' && define.amd) {
18		// AMD. Register as an anonymous module.
19		define(factory);
20	} else if (typeof exports === 'object') {
21		// CommonJS
22		module.exports = factory();
23	} else {
24		// Browser global
25		root.ScrollMagic = factory();
26	}
27}(this, function () {
28	"use strict";
29
30	var ScrollMagic = function () {
31		_util.log(2, '(COMPATIBILITY NOTICE) -> As of ScrollMagic 2.0.0 you need to use \'new ScrollMagic.Controller()\' to create a new controller instance. Use \'new ScrollMagic.Scene()\' to instance a scene.');
32	};
33
34	ScrollMagic.version = "2.0.7";
35
36	// TODO: temporary workaround for chrome's scroll jitter bug
37	window.addEventListener("mousewheel", function () {});
38
39	// global const
40	var PIN_SPACER_ATTRIBUTE = "data-scrollmagic-pin-spacer";
41
42	/**
43	 * The main class that is needed once per scroll container.
44	 *
45	 * @class
46	 *
47	 * @example
48	 * // basic initialization
49	 * var controller = new ScrollMagic.Controller();
50	 *
51	 * // passing options
52	 * var controller = new ScrollMagic.Controller({container: "#myContainer", loglevel: 3});
53	 *
54	 * @param {object} [options] - An object containing one or more options for the controller.
55	 * @param {(string|object)} [options.container=window] - A selector, DOM object that references the main container for scrolling.
56	 * @param {boolean} [options.vertical=true] - Sets the scroll mode to vertical (`true`) or horizontal (`false`) scrolling.
57	 * @param {object} [options.globalSceneOptions={}] - These options will be passed to every Scene that is added to the controller using the addScene method. For more information on Scene options see {@link ScrollMagic.Scene}.
58	 * @param {number} [options.loglevel=2] Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic.
59											 ** `0` => silent
60											 ** `1` => errors
61											 ** `2` => errors, warnings
62											 ** `3` => errors, warnings, debuginfo
63	 * @param {boolean} [options.refreshInterval=100] - Some changes don't call events by default, like changing the container size or moving a scene trigger element.  
64	 																										 This interval polls these parameters to fire the necessary events.  
65	 																										 If you don't use custom containers, trigger elements or have static layouts, where the positions of the trigger elements don't change, you can set this to 0 disable interval checking and improve performance.
66	 *
67	 */
68	ScrollMagic.Controller = function (options) {
69		/*
70		 * ----------------------------------------------------------------
71		 * settings
72		 * ----------------------------------------------------------------
73		 */
74		var
75			NAMESPACE = 'ScrollMagic.Controller',
76			SCROLL_DIRECTION_FORWARD = 'FORWARD',
77			SCROLL_DIRECTION_REVERSE = 'REVERSE',
78			SCROLL_DIRECTION_PAUSED = 'PAUSED',
79			DEFAULT_OPTIONS = CONTROLLER_OPTIONS.defaults;
80
81		/*
82		 * ----------------------------------------------------------------
83		 * private vars
84		 * ----------------------------------------------------------------
85		 */
86		var
87			Controller = this,
88			_options = _util.extend({}, DEFAULT_OPTIONS, options),
89			_sceneObjects = [],
90			_updateScenesOnNextCycle = false, // can be boolean (true => all scenes) or an array of scenes to be updated
91			_scrollPos = 0,
92			_scrollDirection = SCROLL_DIRECTION_PAUSED,
93			_isDocument = true,
94			_viewPortSize = 0,
95			_enabled = true,
96			_updateTimeout,
97			_refreshTimeout;
98
99		/*
100		 * ----------------------------------------------------------------
101		 * private functions
102		 * ----------------------------------------------------------------
103		 */
104
105		/**
106		 * Internal constructor function of the ScrollMagic Controller
107		 * @private
108		 */
109		var construct = function () {
110			for (var key in _options) {
111				if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
112					log(2, "WARNING: Unknown option \"" + key + "\"");
113					delete _options[key];
114				}
115			}
116			_options.container = _util.get.elements(_options.container)[0];
117			// check ScrollContainer
118			if (!_options.container) {
119				log(1, "ERROR creating object " + NAMESPACE + ": No valid scroll container supplied");
120				throw NAMESPACE + " init failed."; // cancel
121			}
122			_isDocument = _options.container === window || _options.container === document.body || !document.body.contains(_options.container);
123			// normalize to window
124			if (_isDocument) {
125				_options.container = window;
126			}
127			// update container size immediately
128			_viewPortSize = getViewportSize();
129			// set event handlers
130			_options.container.addEventListener("resize", onChange);
131			_options.container.addEventListener("scroll", onChange);
132
133			var ri = parseInt(_options.refreshInterval, 10);
134			_options.refreshInterval = _util.type.Number(ri) ? ri : DEFAULT_OPTIONS.refreshInterval;
135			scheduleRefresh();
136
137			log(3, "added new " + NAMESPACE + " controller (v" + ScrollMagic.version + ")");
138		};
139
140		/**
141		 * Schedule the next execution of the refresh function
142		 * @private
143		 */
144		var scheduleRefresh = function () {
145			if (_options.refreshInterval > 0) {
146				_refreshTimeout = window.setTimeout(refresh, _options.refreshInterval);
147			}
148		};
149
150		/**
151		 * Default function to get scroll pos - overwriteable using `Controller.scrollPos(newFunction)`
152		 * @private
153		 */
154		var getScrollPos = function () {
155			return _options.vertical ? _util.get.scrollTop(_options.container) : _util.get.scrollLeft(_options.container);
156		};
157
158		/**
159		 * Returns the current viewport Size (width vor horizontal, height for vertical)
160		 * @private
161		 */
162		var getViewportSize = function () {
163			return _options.vertical ? _util.get.height(_options.container) : _util.get.width(_options.container);
164		};
165
166		/**
167		 * Default function to set scroll pos - overwriteable using `Controller.scrollTo(newFunction)`
168		 * Make available publicly for pinned mousewheel workaround.
169		 * @private
170		 */
171		var setScrollPos = this._setScrollPos = function (pos) {
172			if (_options.vertical) {
173				if (_isDocument) {
174					window.scrollTo(_util.get.scrollLeft(), pos);
175				} else {
176					_options.container.scrollTop = pos;
177				}
178			} else {
179				if (_isDocument) {
180					window.scrollTo(pos, _util.get.scrollTop());
181				} else {
182					_options.container.scrollLeft = pos;
183				}
184			}
185		};
186
187		/**
188		 * Handle updates in cycles instead of on scroll (performance)
189		 * @private
190		 */
191		var updateScenes = function () {
192			if (_enabled && _updateScenesOnNextCycle) {
193				// determine scenes to update
194				var scenesToUpdate = _util.type.Array(_updateScenesOnNextCycle) ? _updateScenesOnNextCycle : _sceneObjects.slice(0);
195				// reset scenes
196				_updateScenesOnNextCycle = false;
197				var oldScrollPos = _scrollPos;
198				// update scroll pos now instead of onChange, as it might have changed since scheduling (i.e. in-browser smooth scroll)
199				_scrollPos = Controller.scrollPos();
200				var deltaScroll = _scrollPos - oldScrollPos;
201				if (deltaScroll !== 0) { // scroll position changed?
202					_scrollDirection = (deltaScroll > 0) ? SCROLL_DIRECTION_FORWARD : SCROLL_DIRECTION_REVERSE;
203				}
204				// reverse order of scenes if scrolling reverse
205				if (_scrollDirection === SCROLL_DIRECTION_REVERSE) {
206					scenesToUpdate.reverse();
207				}
208				// update scenes
209				scenesToUpdate.forEach(function (scene, index) {
210					log(3, "updating Scene " + (index + 1) + "/" + scenesToUpdate.length + " (" + _sceneObjects.length + " total)");
211					scene.update(true);
212				});
213				if (scenesToUpdate.length === 0 && _options.loglevel >= 3) {
214					log(3, "updating 0 Scenes (nothing added to controller)");
215				}
216			}
217		};
218
219		/**
220		 * Initializes rAF callback
221		 * @private
222		 */
223		var debounceUpdate = function () {
224			_updateTimeout = _util.rAF(updateScenes);
225		};
226
227		/**
228		 * Handles Container changes
229		 * @private
230		 */
231		var onChange = function (e) {
232			log(3, "event fired causing an update:", e.type);
233			if (e.type == "resize") {
234				// resize
235				_viewPortSize = getViewportSize();
236				_scrollDirection = SCROLL_DIRECTION_PAUSED;
237			}
238			// schedule update
239			if (_updateScenesOnNextCycle !== true) {
240				_updateScenesOnNextCycle = true;
241				debounceUpdate();
242			}
243		};
244
245		var refresh = function () {
246			if (!_isDocument) {
247				// simulate resize event. Only works for viewport relevant param (performance)
248				if (_viewPortSize != getViewportSize()) {
249					var resizeEvent;
250					try {
251						resizeEvent = new Event('resize', {
252							bubbles: false,
253							cancelable: false
254						});
255					} catch (e) { // stupid IE
256						resizeEvent = document.createEvent("Event");
257						resizeEvent.initEvent("resize", false, false);
258					}
259					_options.container.dispatchEvent(resizeEvent);
260				}
261			}
262			_sceneObjects.forEach(function (scene, index) { // refresh all scenes
263				scene.refresh();
264			});
265			scheduleRefresh();
266		};
267
268		/**
269		 * Send a debug message to the console.
270		 * provided publicly with _log for plugins
271		 * @private
272		 *
273		 * @param {number} loglevel - The loglevel required to initiate output for the message.
274		 * @param {...mixed} output - One or more variables that should be passed to the console.
275		 */
276		var log = this._log = function (loglevel, output) {
277			if (_options.loglevel >= loglevel) {
278				Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ") ->");
279				_util.log.apply(window, arguments);
280			}
281		};
282		// for scenes we have getters for each option, but for the controller we don't, so we need to make it available externally for plugins
283		this._options = _options;
284
285		/**
286		 * Sort scenes in ascending order of their start offset.
287		 * @private
288		 *
289		 * @param {array} ScenesArray - an array of ScrollMagic Scenes that should be sorted
290		 * @return {array} The sorted array of Scenes.
291		 */
292		var sortScenes = function (ScenesArray) {
293			if (ScenesArray.length <= 1) {
294				return ScenesArray;
295			} else {
296				var scenes = ScenesArray.slice(0);
297				scenes.sort(function (a, b) {
298					return a.scrollOffset() > b.scrollOffset() ? 1 : -1;
299				});
300				return scenes;
301			}
302		};
303
304		/**
305		 * ----------------------------------------------------------------
306		 * public functions
307		 * ----------------------------------------------------------------
308		 */
309
310		/**
311		 * Add one ore more scene(s) to the controller.  
312		 * This is the equivalent to `Scene.addTo(controller)`.
313		 * @public
314		 * @example
315		 * // with a previously defined scene
316		 * controller.addScene(scene);
317		 *
318		 * // with a newly created scene.
319		 * controller.addScene(new ScrollMagic.Scene({duration : 0}));
320		 *
321		 * // adding multiple scenes
322		 * controller.addScene([scene, scene2, new ScrollMagic.Scene({duration : 0})]);
323		 *
324		 * @param {(ScrollMagic.Scene|array)} newScene - ScrollMagic Scene or Array of Scenes to be added to the controller.
325		 * @return {Controller} Parent object for chaining.
326		 */
327		this.addScene = function (newScene) {
328			if (_util.type.Array(newScene)) {
329				newScene.forEach(function (scene, index) {
330					Controller.addScene(scene);
331				});
332			} else if (newScene instanceof ScrollMagic.Scene) {
333				if (newScene.controller() !== Controller) {
334					newScene.addTo(Controller);
335				} else if (_sceneObjects.indexOf(newScene) < 0) {
336					// new scene
337					_sceneObjects.push(newScene); // add to array
338					_sceneObjects = sortScenes(_sceneObjects); // sort
339					newScene.on("shift.controller_sort", function () { // resort whenever scene moves
340						_sceneObjects = sortScenes(_sceneObjects);
341					});
342					// insert Global defaults.
343					for (var key in _options.globalSceneOptions) {
344						if (newScene[key]) {
345							newScene[key].call(newScene, _options.globalSceneOptions[key]);
346						}
347					}
348					log(3, "adding Scene (now " + _sceneObjects.length + " total)");
349				}
350			} else {
351				log(1, "ERROR: invalid argument supplied for '.addScene()'");
352			}
353			return Controller;
354		};
355
356		/**
357		 * Remove one ore more scene(s) from the controller.  
358		 * This is the equivalent to `Scene.remove()`.
359		 * @public
360		 * @example
361		 * // remove a scene from the controller
362		 * controller.removeScene(scene);
363		 *
364		 * // remove multiple scenes from the controller
365		 * controller.removeScene([scene, scene2, scene3]);
366		 *
367		 * @param {(ScrollMagic.Scene|array)}
367 Scene - ScrollMagic Scene or Array of Scenes to be removed from the controller.
368		 * @returns {Controller} Parent object for chaining.
369		 */
370		this.removeScene = function (Scene) {
371			if (_util.type.Array(Scene)) {
372				Scene.forEach(function (scene, index) {
373					Controller.removeScene(scene);
374				});
375			} else {
376				var index = _sceneObjects.indexOf(Scene);
377				if (index > -1) {
378					Scene.off("shift.controller_sort");
379					_sceneObjects.splice(index, 1);
380					log(3, "removing Scene (now " + _sceneObjects.length + " left)");
381					Scene.remove();
382				}
383			}
384			return Controller;
385		};
386
387		/**
388	 * Update one ore more scene(s) according to the scroll position of the container.  
389	 * This is the equivalent to `Scene.update()`.  
390	 * The update method calculates the scene's start and end position (based on the trigger element, trigger hook, duration and offset) and checks it against the current scroll position of the container.  
391	 * It then updates the current scene state accordingly (or does nothing, if the state is already correct) – Pins will be set to their correct position and tweens will be updated to their correct progress.  
392	 * _**Note:** This method gets called constantly whenever Controller detects a change. The only application for you is if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._
393	 * @public
394	 * @example
395	 * // update a specific scene on next cycle
396 	 * controller.updateScene(scene);
397 	 *
398	 * // update a specific scene immediately
399	 * controller.updateScene(scene, true);
400 	 *
401	 * // update multiple scenes scene on next cycle
402	 * controller.updateScene([scene1, scene2, scene3]);
403	 *
404	 * @param {ScrollMagic.Scene} Scene - ScrollMagic Scene or Array of Scenes that is/are supposed to be updated.
405	 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle.  
406	 										  This is useful when changing multiple properties of the scene - this way it will only be updated once all new properties are set (updateScenes).
407	 * @return {Controller} Parent object for chaining.
408	 */
409		this.updateScene = function (Scene, immediately) {
410			if (_util.type.Array(Scene)) {
411				Scene.forEach(function (scene, index) {
412					Controller.updateScene(scene, immediately);
413				});
414			} else {
415				if (immediately) {
416					Scene.update(true);
417				} else if (_updateScenesOnNextCycle !== true && Scene instanceof ScrollMagic.Scene) { // if _updateScenesOnNextCycle is true, all connected scenes are already scheduled for update
418					// prep array for next update cycle
419					_updateScenesOnNextCycle = _updateScenesOnNextCycle || [];
420					if (_updateScenesOnNextCycle.indexOf(Scene) == -1) {
421						_updateScenesOnNextCycle.push(Scene);
422					}
423					_updateScenesOnNextCycle = sortScenes(_updateScenesOnNextCycle); // sort
424					debounceUpdate();
425				}
426			}
427			return Controller;
428		};
429
430		/**
431		 * Updates the controller params and calls updateScene on every scene, that is attached to the controller.  
432		 * See `Controller.updateScene()` for more information about what this means.  
433		 * In most cases you will not need this function, as it is called constantly, whenever ScrollMagic detects a state change event, like resize or scroll.  
434		 * The only application for this method is when ScrollMagic fails to detect these events.  
435		 * One application is with some external scroll libraries (like iScroll) that move an internal container to a negative offset instead of actually scrolling. In this case the update on the controller needs to be called whenever the child container's position changes.
436		 * For this case there will also be the need to provide a custom function to calculate the correct scroll position. See `Controller.scrollPos()` for details.
437		 * @public
438		 * @example
439		 * // update the controller on next cycle (saves performance due to elimination of redundant updates)
440		 * controller.update();
441		 *
442		 * // update the controller immediately
443		 * controller.update(true);
444		 *
445		 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle (better performance)
446		 * @return {Controller} Parent object for chaining.
447		 */
448		this.update = function (immediately) {
449			onChange({
450				type: "resize"
451			}); // will update size and set _updateScenesOnNextCycle to true
452			if (immediately) {
453				updateScenes();
454			}
455			return Controller;
456		};
457
458		/**
459		 * Scroll to a numeric scroll offset, a DOM element, the start of a scene or provide an alternate method for scrolling.  
460		 * For vertical controllers it will change the top scroll offset and for horizontal applications it will change the left offset.
461		 * @public
462		 *
463		 * @since 1.1.0
464		 * @example
465		 * // scroll to an offset of 100
466		 * controller.scrollTo(100);
467		 *
468		 * // scroll to a DOM element
469		 * controller.scrollTo("#anchor");
470		 *
471		 * // scroll to the beginning of a scene
472		 * var scene = new ScrollMagic.Scene({offset: 200});
473		 * controller.scrollTo(scene);
474		 *
475		 * // define a new scroll position modification function (jQuery animate instead of jump)
476		 * controller.scrollTo(function (newScrollPos) {
477		 *	$("html, body").animate({scrollTop: newScrollPos});
478		 * });
479		 * controller.scrollTo(100); // call as usual, but the new function will be used instead
480		 *
481		 * // define a new scroll function with an additional parameter
482		 * controller.scrollTo(function (newScrollPos, message) {
483		 *  console.log(message);
484		 *	$(this).animate({scrollTop: newScrollPos});
485		 * });
486		 * // call as usual, but supply an extra parameter to the defined custom function
487		 * controller.scrollTo(100, "my message");
488		 *
489		 * // define a new scroll function with an additional parameter containing multiple variables
490		 * controller.scrollTo(function (newScrollPos, options) {
491		 *  someGlobalVar = options.a + options.b;
492		 *	$(this).animate({scrollTop: newScrollPos});
493		 * });
494		 * // call as usual, but supply an extra parameter containing multiple options
495		 * controller.scrollTo(100, {a: 1, b: 2});
496		 *
497		 * // define a new scroll function with a callback supplied as an additional parameter
498		 * controller.scrollTo(function (newScrollPos, callback) {
499		 *	$(this).animate({scrollTop: newScrollPos}, 400, "swing", callback);
500		 * });
501		 * // call as usual, but supply an extra parameter, which is used as a callback in the previously defined custom scroll function
502		 * controller.scrollTo(100, function() {
503		 *	console.log("scroll has finished.");
504		 * });
505		 *
506		 * @param {mixed} scrollTarget - The supplied argument can be one of these types:
507		 * 1. `number` -> The container will scroll to this new scroll offset.
508		 * 2. `string` or `object` -> Can be a selector or a DOM object.  
509		 *  The container will scroll to the position of this element.
510		 * 3. `ScrollMagic Scene` -> The container will scroll to the start of this scene.
511		 * 4. `function` -> This function will be used for future scroll position modifications.  
512		 *  This provides a way for you to change the behaviour of scrolling and adding new behaviour like animation. The function receives the new scroll position as a parameter and a reference to the container element using `this`.  
513		 *  It may also optionally receive an optional additional parameter (see below)  
514		 *  _**NOTE:**  
515		 *  All other options will still work as expected, using the new function to scroll._
516		 * @param {mixed} [additionalParameter] - If a custom scroll function was defined (see above 4.), you may want to supply additional parameters to it, when calling it. You can do this using this parameter – see examples for details. Please note, that this parameter will have no effect, if you use the default scrolling function.
517		 * @returns {Controller} Parent object for chaining.
518		 */
519		this.scrollTo = function (scrollTarget, additionalParameter) {
520			if (_util.type.Number(scrollTarget)) { // excecute
521				setScrollPos.call(_options.container, scrollTarget, additionalParameter);
522			} else if (scrollTarget instanceof ScrollMagic.Scene) { // scroll to scene
523				if (scrollTarget.controller() === Controller) { // check if the controller is associated with this scene
524					Controller.scrollTo(scrollTarget.scrollOffset(), additionalParameter);
525				} else {
526					log(2, "scrollTo(): The supplied scene does not belong to this controller. Scroll cancelled.", scrollTarget);
527				}
528			} else if (_util.type.Function(scrollTarget)) { // assign new scroll function
529				setScrollPos = scrollTarget;
530			} else { // scroll to element
531				var elem = _util.get.elements(scrollTarget)[0];
532				if (elem) {
533					// if parent is pin spacer, use spacer position instead so correct start position is returned for pinned elements.
534					while (elem.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) {
535						elem = elem.parentNode;
536					}
537
538					var
539						param = _options.vertical ? "top" : "left", // which param is of interest ?
540						containerOffset = _util.get.offset(_options.container), // container position is needed because element offset is returned in relation to document, not in relation to container.
541						elementOffset = _util.get.offset(elem);
542
543					if (!_isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent
544						containerOffset[param] -= Controller.scrollPos();
545					}
546
547					Controller.scrollTo(elementOffset[param] - containerOffset[param], additionalParameter);
548				} else {
549					log(2, "scrollTo(): The supplied argument is invalid. Scroll cancelled.", scrollTarget);
550				}
551			}
552			return Controller;
553		};
554
555		/**
556		 * **Get** the current scrollPosition or **Set** a new method to calculate it.  
557		 * -> **GET**:
558		 * When used as a getter this function will return the current scroll position.  
559		 * To get a cached value use Controller.info("scrollPos"), which will be updated in the update cycle.  
560		 * For vertical controllers it will return the top scroll offset and for horizontal applications it will return the left offset.
561		 *
562		 * -> **SET**:
563		 * When used as a setter this method prodes a way to permanently overwrite the controller's scroll position calculation.  
564		 * A typical usecase is when the scroll position is not reflected by the containers scrollTop or scrollLeft values, but for example by the inner offset of a child container.  
565		 * Moving a child container inside a parent is a commonly used method for several scrolling frameworks, including iScroll.  
566		 * By providing an alternate calculation function you can make sure ScrollMagic receives the correct scroll position.  
567		 * Please also bear in mind that your function should return y values for vertical scrolls an x for horizontals.
568		 *
569		 * To change the current scroll position please use `Controller.scrollTo()`.
570		 * @public
571		 *
572		 * @example
573		 * // get the current scroll Position
574		 * var scrollPos = controller.scrollPos();
575		 *
576		 * // set a new scroll position calculation method
577		 * controller.scrollPos(function () {
578		 *	return this.info("vertical") ? -mychildcontainer.y : -mychildcontainer.x
579		 * });
580		 *
581		 * @param {function} [scrollPosMethod] - The function to be used for the scroll position calculation of the container.
582		 * @returns {(number|Controller)} Current scroll position or parent object for chaining.
583		 */
584		this.scrollPos = function (scrollPosMethod) {
585			if (!arguments.length) { // get
586				return getScrollPos.call(Controller);
587			} else { // set
588				if (_util.type.Function(scrollPosMethod)) {
589					getScrollPos = scrollPosMethod;
590				} else {
591					log(2, "Provided value for method 'scrollPos' is not a function. To change the current scroll position use 'scrollTo()'.");
592				}
593			}
594			return Controller;
595		};
596
597		/**
598		 * **Get** all infos or one in particular about the controller.
599		 * @public
600		 * @example
601		 * // returns the current scroll position (number)
602		 * var scrollPos = controller.info("scrollPos");
603		 *
604		 * // returns all infos as an object
605		 * var infos = controller.info();
606		 *
607		 * @param {string} [about] - If passed only this info will be returned instead of an object c
607ontaining all.  
608		 							 Valid options are:
609		 							 ** `"size"` => the current viewport size of the container
610		 							 ** `"vertical"` => true if vertical scrolling, otherwise false
611		 							 ** `"scrollPos"` => the current scroll position
612		 							 ** `"scrollDirection"` => the last known direction of the scroll
613		 							 ** `"container"` => the container element
614		 							 ** `"isDocument"` => true if container element is the document.
615		 * @returns {(mixed|object)} The requested info(s).
616		 */
617		this.info = function (about) {
618			var values = {
619				size: _viewPortSize, // contains height or width (in regard to orientation);
620				vertical: _options.vertical,
621				scrollPos: _scrollPos,
622				scrollDirection: _scrollDirection,
623				container: _options.container,
624				isDocument: _isDocument
625			};
626			if (!arguments.length) { // get all as an object
627				return values;
628			} else if (values[about] !== undefined) {
629				return values[about];
630			} else {
631				log(1, "ERROR: option \"" + about + "\" is not available");
632				return;
633			}
634		};
635
636		/**
637		 * **Get** or **Set** the current loglevel option value.
638		 * @public
639		 *
640		 * @example
641		 * // get the current value
642		 * var loglevel = controller.loglevel();
643		 *
644		 * // set a new value
645		 * controller.loglevel(3);
646		 *
647		 * @param {number} [newLoglevel] - The new loglevel setting of the Controller. `[0-3]`
648		 * @returns {(number|Controller)} Current loglevel or parent object for chaining.
649		 */
650		this.loglevel = function (newLoglevel) {
651			if (!arguments.length) { // get
652				return _options.loglevel;
653			} else if (_options.loglevel != newLoglevel) { // set
654				_options.loglevel = newLoglevel;
655			}
656			return Controller;
657		};
658
659		/**
660		 * **Get** or **Set** the current enabled state of the controller.  
661		 * This can be used to disable all Scenes connected to the controller without destroying or removing them.
662		 * @public
663		 *
664		 * @example
665		 * // get the current value
666		 * var enabled = controller.enabled();
667		 *
668		 * // disable the controller
669		 * controller.enabled(false);
670		 *
671		 * @param {boolean} [newState] - The new enabled state of the controller `true` or `false`.
672		 * @returns {(boolean|Controller)} Current enabled state or parent object for chaining.
673		 */
674		this.enabled = function (newState) {
675			if (!arguments.length) { // get
676				return _enabled;
677			} else if (_enabled != newState) { // set
678				_enabled = !!newState;
679				Controller.updateScene(_sceneObjects, true);
680			}
681			return Controller;
682		};
683
684		/**
685		 * Destroy the Controller, all Scenes and everything.
686		 * @public
687		 *
688		 * @example
689		 * // without resetting the scenes
690		 * controller = controller.destroy();
691		 *
692		 * // with scene reset
693		 * controller = controller.destroy(true);
694		 *
695		 * @param {boolean} [resetScenes=false] - If `true` the pins and tweens (if existent) of all scenes will be reset.
696		 * @returns {null} Null to unset handler variables.
697		 */
698		this.destroy = function (resetScenes) {
699			window.clearTimeout(_refreshTimeout);
700			var i = _sceneObjects.length;
701			while (i--) {
702				_sceneObjects[i].destroy(resetScenes);
703			}
704			_options.container.removeEventListener("resize", onChange);
705			_options.container.removeEventListener("scroll", onChange);
706			_util.cAF(_updateTimeout);
707			log(3, "destroyed " + NAMESPACE + " (reset: " + (resetScenes ? "true" : "false") + ")");
708			return null;
709		};
710
711		// INIT
712		construct();
713		return Controller;
714	};
715
716	// store pagewide controller options
717	var CONTROLLER_OPTIONS = {
718		defaults: {
719			container: window,
720			vertical: true,
721			globalSceneOptions: {},
722			loglevel: 2,
723			refreshInterval: 100
724		}
725	};
726	/*
727	 * method used to add an option to ScrollMagic Scenes.
728	 */
729	ScrollMagic.Controller.addOption = function (name, defaultValue) {
730		CONTROLLER_OPTIONS.defaults[name] = defaultValue;
731	};
732	// instance extension function for plugins
733	ScrollMagic.Controller.extend = function (extension) {
734		var oldClass = this;
735		ScrollMagic.Controller = function () {
736			oldClass.apply(this, arguments);
737			this.$super = _util.extend({}, this); // copy parent state
738			return extension.apply(this, arguments) || this;
739		};
740		_util.extend(ScrollMagic.Controller, oldClass); // copy properties
741		ScrollMagic.Controller.prototype = oldClass.prototype; // copy prototype
742		ScrollMagic.Controller.prototype.constructor = ScrollMagic.Controller; // restore constructor
743	};
744
745
746	/**
747	 * A Scene defines where the controller should react and how.
748	 *
749	 * @class
750	 *
751	 * @example
752	 * // create a standard scene and add it to a controller
753	 * new ScrollMagic.Scene()
754	 *		.addTo(controller);
755	 *
756	 * // create a scene with custom options and assign a handler to it.
757	 * var scene = new ScrollMagic.Scene({
758	 * 		duration: 100,
759	 *		offset: 200,
760	 *		triggerHook: "onEnter",
761	 *		reverse: false
762	 * });
763	 *
764	 * @param {object} [options] - Options for the Scene. The options can be updated at any time.  
765	 							   Instead of setting the options for each scene individually you can also set them globally in the controller as the controllers `globalSceneOptions` option. The object accepts the same properties as the ones below.  
766	 							   When a scene is added to the controller the options defined using the Scene constructor will be overwritten by those set in `globalSceneOptions`.
767	 * @param {(number|string|function)} [options.duration=0] - The duration of the scene. 
768	 					Please see `Scene.duration()` for details.
769	 * @param {number} [options.offset=0] - Offset Value for the Trigger Position. If no triggerElement is defined this will be the scroll distance from the start of the page, after which the scene will start.
770	 * @param {(string|object)} [options.triggerElement=null] - Selector or DOM object that defines the start of the scene. If undefined the scene will start right at the start of the page (unless an offset is set).
771	 * @param {(number|string)} [options.triggerHook="onCenter"] - Can be a number between 0 and 1 defining the position of the trigger Hook in relation to the viewport.  
772	 															  Can also be defined using a string:
773	 															  ** `"onEnter"` => `1`
774	 															  ** `"onCenter"` => `0.5`
775	 															  ** `"onLeave"` => `0`
776	 * @param {boolean} [options.reverse=true] - Should the scene reverse, when scrolling up?
777	 * @param {number} [options.loglevel=2] - Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic.
778	 										  ** `0` => silent
779	 										  ** `1` => errors
780	 										  ** `2` => errors, warnings
781	 										  ** `3` => errors, warnings, debuginfo
782	 * 
783	 */
784	ScrollMagic.Scene = function (options) {
785
786		/*
787		 * ----------------------------------------------------------------
788		 * settings
789		 * ----------------------------------------------------------------
790		 */
791
792		var
793			NAMESPACE = 'ScrollMagic.Scene',
794			SCENE_STATE_BEFORE = 'BEFORE',
795			SCENE_STATE_DURING = 'DURING',
796			SCENE_STATE_AFTER = 'AFTER',
797			DEFAULT_OPTIONS = SCENE_OPTIONS.defaults;
798
799		/*
800		 * ----------------------------------------------------------------
801		 * private vars
802		 * ----------------------------------------------------------------
803		 */
804
805		var
806			Scene = this,
807			_options = _util.extend({}, DEFAULT_OPTIONS, options),
808			_state = SCENE_STATE_BEFORE,
809			_progress = 0,
810			_scrollOffset = {
811				start: 0,
812				end: 0
813			}, // reflects the controllers's scroll position for the start and end of the scene respectively
814			_triggerPos = 0,
815			_enabled = true,
816			_durationUpdateMethod,
817			_controller;
818
819		/**
820		 * Internal constructor function of the ScrollMagic Scene
821		 * @private
822		 */
823		var construct = function () {
824			for (var key in _options) { // check supplied options
825				if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
826					log(2, "WARNING: Unknown option \"" + key + "\"");
827					delete _options[key];
828				}
829			}
830			// add getters/setters for all possible options
831			for (var optionName in DEFAULT_OPTIONS) {
832				addSceneOption(optionName);
833			}
834			// validate all options
835			validateOption();
836		};
837
838		/*
839		 * ----------------------------------------------------------------
840		 * Event Management
841		 * ----------------------------------------------------------------
842		 */
843
844		var _listeners = {};
845		/**
846		 * Scene start event.  
847		 * Fires whenever the scroll position its the starting point of the scene.  
848		 * It will also fire when scrolling back up going over the start position of the scene. If you want something to happen only when scrolling down/right, use the scrollDirection parameter passed to the callback.
849		 *
850		 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method.
851		 *
852		 * @event ScrollMagic.Scene#start
853		 *
854		 * @example
855		 * scene.on("start", function (event) {
856		 * 	console.log("Hit start point of scene.");
857		 * });
858		 *
859		 * @property {object} event - The event Object passed to each callback
860		 * @property {string} event.type - The name of the event
861		 * @property {Scene} event.target - The Scene object that triggered this event
862		 * @property {number} event.progress - Reflects the current progress of the scene
863		 * @property {string} event.state - The current state of the scene `"BEFORE"` or `"DURING"`
864		 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
865		 */
866		/**
867		 * Scene end event.  
868		 * Fires whenever the scroll position its the ending point of the scene.  
869		 * It will also fire when scrolling back up from after the scene and going over its end position. If you want something to happen only when scrolling down/right, use the scrollDirection parameter passed to the callback.
870		 *
871		 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method.
872		 *
873		 * @event ScrollMagic.Scene#end
874		 *
875		 * @example
876		 * scene.on("end", function (event) {
877		 * 	console.log("Hit end point of scene.");
878		 * });
879		 *
880		 * @property {object} event - The event Object passed to each callback
881		 * @property {string} event.type - The name of the event
882		 * @property {Scene} event.target - The Scene object that triggered this event
883		 * @property {number} event.progress - Reflects the current progress of the scene
884		 * @property {string} event.state - The current state of the scene `"DURING"` or `"AFTER"`
885		 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
886		 */
887		/**
888		 * Scene enter event.  
889		 * Fires whenever the scene enters the "DURING" state.  
890		 * Keep in mind that it doesn't matter if the scene plays forward or backward: This event always fires when the scene enters its active scroll timeframe, regardless of the scroll-direction.
891		 *
892		 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method.
893		 *
894		 * @event ScrollMagic.Scene#enter
895		 *
896		 * @example
897		 * scene.on("enter", function (event) {
898		 * 	console.log("Scene entered.");
899		 * });
900		 *
901		 * @property {object} event - The event Object passed to each callback
902		 * @property {string} event.type - The name of the event
903		 * @property {Scene} event.target - The Scene object that triggered this event
904		 * @property {number} event.progress - Reflects the current progress of the scene
905		 * @property {string} event.state - The current state of the scene - always `"DURING"`
906		 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
907		 */
908		/**
909		 * Scene leave event.  
910		 * Fires whenever the scene's state goes from "DURING" to either "BEFORE" or "AFTER".  
911		 * Keep in mind that it doesn't matter if the scene plays forward or backward: This event al
911ways fires when the scene leaves its active scroll timeframe, regardless of the scroll-direction.
912		 *
913		 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method.
914		 *
915		 * @event ScrollMagic.Scene#leave
916		 *
917		 * @example
918		 * scene.on("leave", function (event) {
919		 * 	console.log("Scene left.");
920		 * });
921		 *
922		 * @property {object} event - The event Object passed to each callback
923		 * @property {string} event.type - The name of the event
924		 * @property {Scene} event.target - The Scene object that triggered this event
925		 * @property {number} event.progress - Reflects the current progress of the scene
926		 * @property {string} event.state - The current state of the scene `"BEFORE"` or `"AFTER"`
927		 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
928		 */
929		/**
930		 * Scene update event.  
931		 * Fires whenever the scene is updated (but not necessarily changes the progress).
932		 *
933		 * @event ScrollMagic.Scene#update
934		 *
935		 * @example
936		 * scene.on("update", function (event) {
937		 * 	console.log("Scene updated.");
938		 * });
939		 *
940		 * @property {object} event - The event Object passed to each callback
941		 * @property {string} event.type - The name of the event
942		 * @property {Scene} event.target - The Scene object that triggered this event
943		 * @property {number} event.startPos - The starting position of the scene (in relation to the conainer)
944		 * @property {number} event.endPos - The ending position of the scene (in relation to the conainer)
945		 * @property {number} event.scrollPos - The current scroll position of the container
946		 */
947		/**
948		 * Scene progress event.  
949		 * Fires whenever the progress of the scene changes.
950		 *
951		 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method.
952		 *
953		 * @event ScrollMagic.Scene#progress
954		 *
955		 * @example
956		 * scene.on("progress", function (event) {
957		 * 	console.log("Scene progress changed to " + event.progress);
958		 * });
959		 *
960		 * @property {object} event - The event Object passed to each callback
961		 * @property {string} event.type - The name of the event
962		 * @property {Scene} event.target - The Scene object that triggered this event
963		 * @property {number} event.progress - Reflects the current progress of the scene
964		 * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
965		 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
966		 */
967		/**
968		 * Scene change event.  
969		 * Fires whenvever a property of the scene is changed.
970		 *
971		 * @event ScrollMagic.Scene#change
972		 *
973		 * @example
974		 * scene.on("change", function (event) {
975		 * 	console.log("Scene Property \"" + event.what + "\" changed to " + event.newval);
976		 * });
977		 *
978		 * @property {object} event - The event Object passed to each callback
979		 * @property {string} event.type - The name of the event
980		 * @property {Scene} event.target - The Scene object that triggered this event
981		 * @property {string} event.what - Indicates what value has been changed
982		 * @property {mixed} event.newval - The new value of the changed property
983		 */
984		/**
985		 * Scene shift event.  
986		 * Fires whenvever the start or end **scroll offset** of the scene change.
987		 * This happens explicitely, when one of these values change: `offset`, `duration` or `triggerHook`.
988		 * It will fire implicitly when the `triggerElement` changes, if the new element has a different position (most cases).
989		 * It will also fire implicitly when the size of the container changes and the triggerHook is anything other than `onLeave`.
990		 *
991		 * @event ScrollMagic.Scene#shift
992		 * @since 1.1.0
993		 *
994		 * @example
995		 * scene.on("shift", function (event) {
996		 * 	console.log("Scene moved, because the " + event.reason + " has changed.)");
997		 * });
998		 *
999		 * @property {object} event - The event Object passed to each callback
1000		 * @property {string} event.type - The name of the event
1001		 * @property {Scene} event.target - The Scene object that triggered this event
1002		 * @property {string} event.reason - Indicates why the scene has shifted
1003		 */
1004		/**
1005		 * Scene destroy event.  
1006		 * Fires whenvever the scene is destroyed.
1007		 * This can be used to tidy up custom behaviour used in events.
1008		 *
1009		 * @event ScrollMagic.Scene#destroy
1010		 * @since 1.1.0
1011		 *
1012		 * @example
1013		 * scene.on("enter", function (event) {
1014		 *        // add custom action
1015		 *        $("#my-elem").left("200");
1016		 *      })
1017		 *      .on("destroy", function (event) {
1018		 *        // reset my element to start position
1019		 *        if (event.reset) {
1020		 *          $("#my-elem").left("0");
1021		 *        }
1022		 *      });
1023		 *
1024		 * @property {object} event - The event Object passed to each callback
1025		 * @property {string} event.type - The name of the event
1026		 * @property {Scene} event.target - The Scene object that triggered this event
1027		 * @property {boolean} event.reset - Indicates if the destroy method was called with reset `true` or `false`.
1028		 */
1029		/**
1030		 * Scene add event.  
1031		 * Fires when the scene is added to a controller.
1032		 * This is mostly used by plugins to know that change might be due.
1033		 *
1034		 * @event ScrollMagic.Scene#add
1035		 * @since 2.0.0
1036		 *
1037		 * @example
1038		 * scene.on("add", function (event) {
1039		 * 	console.log('Scene was added to a new controller.');
1040		 * });
1041		 *
1042		 * @property {object} event - The event Object passed to each callback
1043		 * @property {string} event.type - The name of the event
1044		 * @property {Scene} event.target - The Scene object that triggered this event
1045		 * @property {boolean} event.controller - The controller object the scene was added to.
1046		 */
1047		/**
1048		 * Scene remove event.  
1049		 * Fires when the scene is removed from a controller.
1050		 * This is mostly used by plugins to know that change might be due.
1051		 *
1052		 * @event ScrollMagic.Scene#remove
1053		 * @since 2.0.0
1054		 *
1055		 * @example
1056		 * scene.on("remove", function (event) {
1057		 * 	console.log('Scene was removed from its controller.');
1058		 * });
1059		 *
1060		 * @property {object} event - The event Object passed to each callback
1061		 * @property {string} event.type - The name of the event
1062		 * @property {Scene} event.target - The Scene object that triggered this event
1063		 */
1064
1065		/**
1066		 * Add one ore more event listener.  
1067		 * The callback function will be fired at the respective event, and an object c
1067ontaining relevant data will be passed to the callback.
1068		 * @method ScrollMagic.Scene#on
1069		 *
1070		 * @example
1071		 * function callback (event) {
1072		 * 		console.log("Event fired! (" + event.type + ")");
1073		 * }
1074		 * // add listeners
1075		 * scene.on("change update progress start end enter leave", callback);
1076		 *
1077		 * @param {string} names - The name or names of the event the callback should be attached to.
1078		 * @param {function} callback - A function that should be executed, when the event is dispatched. An event object will be passed to the callback.
1079		 * @returns {Scene} Parent object for chaining.
1080		 */
1081		this.on = function (names, callback) {
1082			if (_util.type.Function(callback)) {
1083				names = names.trim().split(' ');
1084				names.forEach(function (fullname) {
1085					var
1086						nameparts = fullname.split('.'),
1087						eventname = nameparts[0],
1088						namespace = nameparts[1];
1089					if (eventname != "*") { // disallow wildcards
1090						if (!_listeners[eventname]) {
1091							_listeners[eventname] = [];
1092						}
1093						_listeners[eventname].push({
1094							namespace: namespace || '',
1095							callback: callback
1096						});
1097					}
1098				});
1099			} else {
1100				log(1, "ERROR when calling '.on()': Supplied callback for '" + names + "' is not a valid function!");
1101			}
1102			return Scene;
1103		};
1104
1105		/**
1106		 * Remove one or more event listener.
1107		 * @method ScrollMagic.Scene#off
1108		 *
1109		 * @example
1110		 * function callback (event) {
1111		 * 		console.log("Event fired! (" + event.type + ")");
1112		 * }
1113		 * // add listeners
1114		 * scene.on("change update", callback);
1115		 * // remove listeners
1116		 * scene.off("change update", callback);
1117		 *
1118		 * @param {string} names - The name or names of the event that should be removed.
1119		 * @param {function} [callback] - A specific callback function that should be removed. If none is passed all callbacks to the event listener will be removed.
1120		 * @returns {Scene} Parent object for chaining.
1121		 */
1122		this.off = function (names, callback) {
1123			if (!names) {
1124				log(1, "ERROR: Invalid event name supplied.");
1125				return Scene;
1126			}
1127			names = names.trim().split(' ');
1128			names.forEach(function (fullname, key) {
1129				var
1130					nameparts = fullname.split('.'),
1131					eventname = nameparts[0],
1132					namespace = nameparts[1] || '',
1133					removeList = eventname === '*' ? Object.keys(_listeners) : [eventname];
1134				removeList.forEach(function (remove) {
1135					var
1136						list = _listeners[remove] || [],
1137						i = list.length;
1138					while (i--) {
1139						var listener = list[i];
1140						if (listener && (namespace === listener.namespace || namespace === '*') && (!callback || callback == listener.callback)) {
1141							list.splice(i, 1);
1142						}
1143					}
1144					if (!list.length) {
1145						delete _listeners[remove];
1146					}
1147				});
1148			});
1149			return Scene;
1150		};
1151
1152		/**
1153		 * Trigger an event.
1154		 * @method ScrollMagic.Scene#trigger
1155		 *
1156		 * @example
1157		 * this.trigger("change");
1158		 *
1159		 * @param {string} name - The name of the event that should be triggered.
1160		 * @param {object} [vars] - An object containing info that should be passed to the callback.
1161		 * @returns {Scene} Parent object for chaining.
1162		 */
1163		this.trigger = function (name, vars) {
1164			if (name) {
1165				var
1166					nameparts = name.trim().split('.'),
1167					eventname = nameparts[0],
1168					namespace = nameparts[1],
1169					listeners = _listeners[eventname];
1170				log(3, 'event fired:', eventname, vars ? "->" : '', vars || '');
1171				if (listeners) {
1172					listeners.forEach(function (listener, key) {
1173						if (!namespace || namespace === listener.namespace) {
1174							listener.callback.call(Scene, new ScrollMagic.Event(eventname, listener.namespace, Scene, vars));
1175						}
1176					});
1177				}
1178			} else {
1179				log(1, "ERROR: Invalid event name supplied.");
1180			}
1181			return Scene;
1182		};
1183
1184		// set event listeners
1185		Scene
1186			.on("change.internal", function (e) {
1187				if (e.what !== "loglevel" && e.what !== "tweenChanges") { // no need for a scene update scene with these options...
1188					if (e.what === "triggerElement") {
1189						updateTriggerElementPosition();
1190					} else if (e.what === "reverse") { // the only property left that may have an impact on the current scene state. Everything else is handled by the shift event.
1191						Scene.update();
1192					}
1193				}
1194			})
1195			.on("shift.internal", function (e) {
1196				updateScrollOffset();
1197				Scene.update(); // update scene to reflect new position
1198			});
1199
1200		/**
1201		 * Send a debug message to the console.
1202		 * @private
1203		 * but provided publicly with _log for plugins
1204		 *
1205		 * @param {number} loglevel - The loglevel required to initiate output for the message.
1206		 * @param {...mixed} output - One or more variables that should be passed to the console.
1207		 */
1208		var log = this._log = function (loglevel, output) {
1209			if (_options.loglevel >= loglevel) {
1210				Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ") ->");
1211				_util.log.apply(window, arguments);
1212			}
1213		};
1214
1215		/**
1216		 * Add the scene to a controller.  
1217		 * This is the equivalent to `Controller.addScene(scene)`.
1218		 * @method ScrollMagic.Scene#addTo
1219		 *
1220		 * @example
1221		 * // add a scene to a ScrollMagic Controller
1222		 * scene.addTo(controller);
1223		 *
1224		 * @param {ScrollMagic.Controller} controller - The controller to which the scene should be added.
1225		 * @returns {Scene} Parent object for chaining.
1226		 */
1227		this.addTo = function (controller) {
1228			if (!(controller instanceof ScrollMagic.Controller)) {
1229				log(1, "ERROR: supplied argument of 'addTo()' is not a valid ScrollMagic Controller");
1230			} else if (_controller != controller) {
1231				// new controller
1232				if (_controller) { // was associated to a different controller before, so remove it...
1233					_controller.removeScene(Scene);
1234				}
1235				_controller = controller;
1236				validateOption();
1237				updateDuration(true);
1238				updateTriggerElementPosition(true);
1239				updateScrollOffset();
1240				_controller.info("container").addEventListener('resize', onContainerResize);
1241				controller.addScene(Scene);
1242				Scene.trigger("add", {
1243					controller: _controller
1244				});
1245				log(3, "added " + NAMESPACE + " to controller");
1246				Scene.update();
1247			}
1248			return Scene;
1249		};
1250
1251		/**
1252		 * **Get** or **Set** the current enabled state of the scene.  
1253		 * This can be used to disable this scene without removing or destroying it.
1254		 * @method ScrollMagic.Scene#enabled
1255		 *
1256		 * @example
1257		 * // get the current value
1258		 * var enabled = scene.enabled();
1259		 *
1260		 * // disable the scene
1261		 * scene.enabled(false);
1262		 *
1263		 * @param {boolean} [newState] - The new enabled state of the scene `true` or `false`.
1264		 * @returns {(boolean|Scene)} Current enabled state or parent object for chaining.
1265		 */
1266		this.enabled = function (newState) {
1267			if (!arguments.length) { // get
1268				return _enabled;
1269			} else if (_enabled != newState) { // set
1270				_enabled = !!newState;
1271				Scene.update(true);
1272			}
1273			return Scene;
1274		};
1275
1276		/**
1277		 * Remove the scene from the controller.  
1278		 * This is the equivalent to `Controller.removeScene(scene)`.
1279		 * The scene will not be updated anymore until you readd it to a controller.
1280		 * To remove the pin or the tween you need to call removeTween() or removePin() respectively.
1281		 * @method ScrollMagic.Scene#remove
1282		 * @example
1283		 * // remove the scene from its controller
1284		 * scene.remove();
1285		 *
1286		 * @returns {Scene} Parent object for chaining.
1287		 */
1288		this.remove = function () {
1289			if (_controller) {
1290				_controller.info("container").removeEventListener('resize', onContainerResize);
1291				var tmpParent = _controller;
1292				_controller = undefined;
1293				tmpParent.removeScene(Scene);
1294				Scene.trigger("remove");
1295				log(3, "removed " + NAMESPACE + " from controller");
1296			}
1297			return Scene;
1298		};
1299
1300		/**
1301		 * Destroy the scene and everything.
1302		 * @method ScrollMagic.Scene#destroy
1303		 * @example
1304		 * // destroy the scene without resetting the pin and tween to their initial positions
1305		 * scene = scene.destroy();
1306		 *
1307		 * // destroy the scene and reset the pin and tween
1308		 * scene = scene.destroy(true);
1309		 *
1310		 * @param {boolean} [reset=false] - If `true` the pin and tween (if existent) will be reset.
1311		 * @returns {null} Null to unset handler variables.
1312		 */
1313		this.destroy = function (reset) {
1314			Scene.trigger("destroy", {
1315				reset: reset
1316			});
1317			Scene.remove();
1318			Scene.off("*.*");
1319			log(3, "destroyed " + NAMESPACE + " (reset: " + (reset ? "true" : "false") + ")");
1320			return null;
1321		};
1322
1323
1324		/**
1325		 * Updates the Scene to reflect the current state.  
1326		 * This is the equivalent to `Controller.updateScene(scene, immediately)`.  
1327		 * The update method calculates the scene's start and end position (based on the trigger element, trigger hook, duration and offset) and checks it against the current scroll position of the container.  
1328		 * It then updates the current scene state accordingly (or does nothing, if the state is already correct) – Pins will be set to their correct position and tweens will be updated to their correct progress.
1329		 * This means an update doesn't necessarily result in a progress change. The `progress` event will be fired if the progress has indeed changed between this update and the last.  
1330		 * _**NOTE:** This method gets called constantly whenever ScrollMagic detects a change. The only application for you is 
1330if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._
1331		 * @method ScrollMagic.Scene#update
1332		 * @example
1333		 * // update the scene on next tick
1334		 * scene.update();
1335		 *
1336		 * // update the scene immediately
1337		 * scene.update(true);
1338		 *
1339		 * @fires Scene.update
1340		 *
1341		 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle (better performance).
1342		 * @returns {Scene} Parent object for chaining.
1343		 */
1344		this.update = function (immediately) {
1345			if (_controller) {
1346				if (immediately) {
1347					if (_controller.enabled() && _enabled) {
1348						var
1349							scrollPos = _controller.info("scrollPos"),
1350							newProgress;
1351
1352						if (_options.duration > 0) {
1353							newProgress = (scrollPos - _scrollOffset.start) / (_scrollOffset.end - _scrollOffset.start);
1354						} else {
1355							newProgress = scrollPos >= _scrollOffset.start ? 1 : 0;
1356						}
1357
1358						Scene.trigger("update", {
1359							startPos: _scrollOffset.start,
1360							endPos: _scrollOffset.end,
1361							scrollPos: scrollPos
1362						});
1363
1364						Scene.progress(newProgress);
1365					} else if (_pin && _state === SCENE_STATE_DURING) {
1366						updatePinState(true); // unpin in position
1367					}
1368				} else {
1369					_controller.updateScene(Scene, false);
1370				}
1371			}
1372			return Scene;
1373		};
1374
1375		/**
1376		 * Updates dynamic scene variables like the trigger element position or the duration.
1377		 * This method is automatically called in regular intervals from the controller. See {@link ScrollMagic.Controller} option `refreshInterval`.
1378		 * 
1379		 * You can call it to minimize lag, for example when you intentionally change the position of the triggerElement.
1380		 * If you don't it will simply be updated in the next refresh interval of the container, which is usually sufficient.
1381		 *
1382		 * @method ScrollMagic.Scene#refresh
1383		 * @since 1.1.0
1384		 * @example
1385		 * scene = new ScrollMagic.Scene({triggerElement: "#trigger"});
1386		 * 
1387		 * // change the position of the trigger
1388		 * $("#trigger").css("top", 500);
1389		 * // immediately let the scene know of this change
1390		 * scene.refresh();
1391		 *
1392		 * @fires {@link Scene.shift}, if the trigger element position or the duration changed
1393		 * @fires {@link Scene.change}, if the duration changed
1394		 *
1395		 * @returns {Scene} Parent object for chaining.
1396		 */
1397		this.refresh = function () {
1398			updateDuration();
1399			updateTriggerElementPosition();
1400			// update trigger element position
1401			return Scene;
1402		};
1403
1404		/**
1405		 * **Get** or **Set** the scene's progress.  
1406		 * Usually it shouldn't be necessary to use this as a setter, as it is set automatically by scene.update().  
1407		 * The order in which the events are fired depends on the duration of the scene:
1408		 *  1. Scenes with `duration == 0`:  
1409		 *  Scenes that have no duration by definition have no ending. Thus the `end` event will never be fired.  
1410		 *  When the trigger position of the scene is passed the events are always fired in this order:  
1411		 *  `enter`, `start`, `progress` when scrolling forward  
1412		 *  and  
1413		 *  `progress`, `start`, `leave` when scrolling in reverse
1414		 *  2. Scenes with `duration > 0`:  
1415		 *  Scenes with a set duration have a defined start and end point.  
1416		 *  When scrolling past the start position of the scene it will fire these events in this order:  
1417		 *  `enter`, `start`, `progress`  
1418		 *  When continuing to scroll and passing the end point it will fire these events:  
1419		 *  `progress`, `end`, `leave`  
1420		 *  When reversing through the end point these events are fired:  
1421		 *  `enter`, `end`, `progress`  
1422		 *  And when continuing to scroll past the start position in reverse it will fire:  
1423		 *  `progress`, `start`, `leave`  
1424		 *  In between start and end the `progress` event will be called constantly, whenever the progress changes.
1425		 * 
1426		 * In short:  
1427		 * `enter` events will always trigger **before** the progress update and `leave` envents will trigger **after** the progress update.  
1428		 * `start` and `end` will always trigger at their respective position.
1429		 * 
1430		 * Please review the event descriptions for details on the events and the event object that is passed to the callback.
1431		 * 
1432		 * @method ScrollMagic.Scene#progress
1433		 * @example
1434		 * // get the current scene progress
1435		 * var progress = scene.progress();
1436		 *
1437		 * // set new scene progress
1438		 * scene.progress(0.3);
1439		 *
1440		 * @fires {@link Scene.enter}, when used as setter
1441		 * @fires {@link Scene.start}, when used as setter
1442		 * @fires {@link Scene.progress}, when used as setter
1443		 * @fires {@link Scene.end}, when used as setter
1444		 * @fires {@link Scene.leave}, when used as setter
1445		 *
1446		 * @param {number} [progress] - The new progress value of the scene `[0-1]`.
1447		 * @returns {number} `get` -  Current scene progress.
1448		 * @returns {Scene} `set` -  Parent object for chaining.
1449		 */
1450		this.progress = function (progress) {
1451			if (!arguments.length) { // get
1452				return _progress;
1453			} else { // set
1454				var
1455					doUpdate = false,
1456					oldState = _state,
1457					scrollDirection = _controller ? _controller.info("scrollDirection") : 'PAUSED',
1458					reverseOrForward = _options.reverse || progress >= _progress;
1459				if (_options.duration === 0) {
1460					// zero duration scenes
1461					doUpdate = _progress != progress;
1462					_progress = progress < 1 && reverseOrForward ? 0 : 1;
1463					_state = _progress === 0 ? SCENE_STATE_BEFORE : SCENE_STATE_DURING;
1464				} else {
1465					// scenes with start and end
1466					if (progress < 0 && _state !== SCENE_STATE_BEFORE && reverseOrForward) {
1467						// go back to initial state
1468						_progress = 0;
1469						_state = SCENE_STATE_BEFORE;
1470						doUpdate = true;
1471					} else if (progress >= 0 && progress < 1 && reverseOrForward) {
1472						_progress = progress;
1473						_state = SCENE_STATE_DURING;
1474						doUpdate = true;
1475					} else if (progress >= 1 && _state !== SCENE_STATE_AFTER) {
1476						_progress = 1;
1477						_state = SCENE_STATE_AFTER;
1478						doUpdate = true;
1479					} else if (_state === SCENE_STATE_DURING && !reverseOrForward) {
1480						updatePinState(); // in case we scrolled backwards mid-scene and reverse is disabled => update the pin position, so it doesn't move back as well.
1481					}
1482				}
1483				if (doUpdate) {
1484					// fire events
1485					var
1486						eventVars = {
1487							progress: _progress,
1488							state: _state,
1489							scrollDirection: scrollDirection
1490						},
1491						stateChanged = _state != oldState;
1492
1493					var trigger = function (eventName) { // tmp helper to simplify code
1494						Scene.trigger(eventName, eventVars);
1495					};
1496
1497					if (stateChanged) { // enter events
1498						if (oldState !== SCENE_STATE_DURING) {
1499							trigger("enter");
1500							trigger(oldState === SCENE_STATE_BEFORE ? "start" : "end");
1501						}
1502					}
1503					trigger("progress");
1504					if (stateChanged) { // leave events
1505						if (_state !== SCENE_STATE_DURING) {
1506							trigger(_state === SCENE_STATE_BEFORE ? "start" : "end");
1507							trigger("leave");
1508						}
1509					}
1510				}
1511
1512				return Scene;
1513			}
1514		};
1515
1516
1517		/**
1518		 * Update the start and end scrollOffset of the container.
1519		 * The positions reflect what the controller's scroll position will be at the start and end respectively.
1520		 * Is called, when:
1521		 *   - Scene event "change" is called with: offset, triggerHook, duration 
1522		 *   - scroll container event "resize" is called
1523		 *   - the position of the triggerElement changes
1524		 *   - the controller changes -> addTo()
1525		 * @private
1526		 */
1527		var updateScrollOffset = function () {
1528			_scrollOffset = {
1529				start: _triggerPos + _options.offset
1530			};
1531			if (_controller && _options.triggerElement) {
1532				// take away triggerHook portion to get relative to top
1533				_scrollOffset.start -= _controller.info("size") * _options.triggerHook;
1534			}
1535			_scrollOffset.end = _scrollOffset.start + _options.duration;
1536		};
1537
1538		/**
1539		 * Updates the duration if set to a dynamic function.
1540		 * This method is called when the scene is added to a controller and in regular intervals from the controller through scene.refresh().
1541		 * 
1542		 * @fires {@link Scene.change}, if the duration changed
1543		 * @fires {@link Scene.shift}, if the duration changed
1544		 *
1545		 * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed.
1546		 * @private
1547		 */
1548		var updateDuration = function (suppressEvents) {
1549			// update duration
1550			if (_durationUpdateMethod) {
1551				var varname = "duration";
1552				if (changeOption(varname, _durationUpdateMethod.call(Scene)) && !suppressEvents) { // set
1553					Scene.trigger("change", {
1554						what: varname,
1555						newval: _options[varname]
1556					});
1557					Scene.trigger("shift", {
1558						reason: varname
1559					});
1560				}
1561			}
1562		};
1563
1564		/**
1565		 * Updates the position of the triggerElement, if present.
1566		 * This method is called ...
1567		 *  - ... when the triggerElement is changed
1568		 *  - ... when the scene is added to a (new) controller
1569		 *  - ... in regular intervals from the controller through scene.refresh().
1570		 * 
1571		 * @fires {@link Scene.shift}, if the position changed
1572		 *
1573		 * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed.
1574		 * @private
1575		 */
1576		var updateTriggerElementPosition = function (suppressEvents) {
1577			var
1578				elementPos = 0,
1579				telem = _options.triggerElement;
1580			if (_controller && (telem || _triggerPos > 0)) { // either an element exists or was removed and the triggerPos is still > 0
1581				if (telem) { // there currently a triggerElement set
1582					if (telem.parentNode) { // check if element is still attached to DOM
1583						var
1584							controllerInfo = _controller.info(),
1585							containerOffset = _util.get.offset(controllerInfo.container), // container position is needed because element offset is returned in relation to document, not in relation to container.
1586							param = controllerInfo.vertical ? "top" : "left"; // which param is of interest ?
1587
1588						// if parent is spacer, use spacer position instead so correct start position is returned for pinned elements.
1589						while (telem.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) {
1590							telem = telem.parentNode;
1591						}
1592
1593						var elementOffset = _util.get.offset(telem);
1594
1595						if (!controllerInfo.isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent
1596							containerOffset[param] -= _controller.scrollPos();
1597						}
1598
1599						elementPos = elementOffset[param] - containerOffset[param];
1600
1601					} else { // there was an element, but it was removed from DOM
1602						log(2, "WARNING: triggerElement was removed from DOM and will be reset to", undefined);
1603						Scene.triggerElement(undefined); // unset, so a change event is triggered
1604					}
1605				}
1606
1607				var changed = elementPos != _triggerPos;
1608				_triggerPos = elementPos;
1609				if (changed && !suppressEvents) {
1610					Scene.trigger("shift", {
1611						reason: "triggerElementPosition"
1612					});
1613				}
1614			}
1615		};
1616
1617		/**
1618		 * Trigger a shift event, when the container is resized and the triggerHook is > 1.
1619		 * @private
1620		 */
1621		var onContainerResize = function (e) {
1622			if (_options.triggerHook > 0) {
1623				Scene.trigger("shift", {
1624					reason: "containerResize"
1625				});
1626			}
1627		};
1628
1629
1630		var _validate = _util.extend(SCENE_OPTIONS.validate, {
1631			// validation for duration handled internally for reference to private var _durationMethod
1632			duration: function (val) {
1633				if (_util.type.String(val) && val.match(/^(\.|\d)*\d+%$/)) {
1634					// percentage value
1635					var perc = parseFloat(val) / 100;
1636					val = function () {
1637						return _controller ? _controller.info("size") * perc : 0;
1638					};
1639				}
1640				if (_util.type.Function(val)) {
1641					// function
1642					_durationUpdateMethod = val;
1643					try {
1644						val = parseFloat(_durationUpdateMethod.call(Scene));
1645					} catch (e) {
1646						val = -1; // will cause error below
1647					}
1648				}
1649				// val has to be float
1650				val = parseFloat(val);
1651				if (!_util.type.Number(val) || val < 0) {
1652					if (_durationUpdateMethod) {
1653						_durationUpdateMethod = undefined;
1654						throw ["Invalid return value of supplied function for option \"duration\":", val];
1655					} else {
1656						throw ["Invalid value for option \"duration\":", val];
1657					}
1658				}
1659				return val;
1660			}
1661		});
1662
1663		/**
1664		 * Checks the validity of a specific or all options and reset to default if neccessary.
1665		 * @private
1666		 */
1667		var validateOption = function (check) {
1668			check = arguments.length ? [check] : Object.keys(_validate);
1669			check.forEach(function (optionName, key) {
1670				var value;
1671				if (_validate[optionName]) { // there is a validation method for this option
1672					try { // validate value
1673						value = _validate[optionName](_options[optionName]);
1674					} catch (e) { // validation failed -> reset to default
1675						value = DEFAULT_OPTIONS[optionName];
1676						var logMSG = _util.type.String(e) ? [e] : e;
1677						if (_util.type.Array(logMSG)) {
1678							logMSG[0] = "ERROR: " + logMSG[0];
1679							logMSG.unshift(1); // loglevel 1 for error msg
1680							log.apply(this, logMSG);
1681						} else {
1682							log(1, "ERROR: Problem executing validation callback for option '" + optionName + "':", e.message);
1683						}
1684					} finally {
1685						_options[optionName] = value;
1686					}
1687				}
1688			});
1689		};
1690
1691		/**
1692		 * Helper used by the setter/getters for scene options
1693		 * @private
1694		 */
1695		var changeOption = function (varname, newval) {
1696			var
1697				changed = false,
1698				oldval = _options[varname];
1699			if (_options[varname] != newval) {
1700				_options[varname] = newval;
1701				validateOption(varname); // resets to default if necessary
1702				changed = oldval != _options[varname];
1703			}
1704			return changed;
1705		};
1706
1707		// generate getters/setters for all options
1708		var addSceneOption = function (optionName) {
1709			if (!Scene[optionName]) {
1710				Scene[optionName] = function (newVal) {
1711					if (!arguments.length) { // get
1712						return _options[optionName];
1713					} else {
1714						if (optionName === "duration") { // new duration is set, so any previously set function must be unset
1715							_durationUpdateMethod = undefined;
1716						}
1717						if (changeOption(optionName, newVal)) { // set
1718							Scene.trigger("change", {
1719								what: optionName,
1720								newval: _options[optionName]
1721							});
1722							if (SCENE_OPTIONS.shifts.indexOf(optionName) > -1) {
1723								Scene.trigger("shift", {
1724									reason: optionName
1725								});
1726							}
1727						}
1728					}
1729					return Scene;
1730				};
1731			}
1732		};
1733
1734		/**
1735		 * **Get** or **Set** the duration option value.
1736		 *
1737		 * As a **setter** it accepts three types of parameters:
1738		 * 1. `number`: Sets the duration of the scene to exactly this amount of pixels.  
1739		 *   This means the scene will last for exactly this amount of pixels scrolled. Sub-Pixels are also valid.
1740		 *   A value of `0` means that the scene is 'open end' and no end will be triggered. Pins will never unpin and animations will play independently of scroll progress.
1741		 * 2. `string`: Always updates the duration relative to parent scroll container.  
1742		 *   For example `"100%"` will keep the duration always exactly at the inner height of the scroll container.
1743		 *   When scrolling vertically the width is used for reference respectively.
1744		 * 3. `function`: The supplied function will be called to return the scene duration.
1745		 *   This is useful in setups where the duration depends on other elements who might change size. By supplying a function you can return a value instead of updating potentially multiple scene durations.  
1746		 *   The scene can be referenced inside the callback using `this`.
1747		 *   _**WARNING:** This is an easy way to kill performance, as the callback will be executed every time `Scene.refresh()` is called, which happens a lot. The interval is defined by the controller (see ScrollMagic.Controller option `refreshInterval`).  
1748		 *   It's recomended to avoid calculations within the function and use cached variables as return values.  
1749		 *   This counts double if you use the same function for multiple scenes._
1750		 *
1751		 * @method ScrollMagic.Scene#duration
1752		 * @example
1753		 * // get the current duration value
1754		 * var duration = scene.duration();
1755		 *
1756		 * // set a new duration
1757		 * scene.duration(300);
1758		 *
1759		 * // set duration responsively to container size
1760		 * scene.duration("100%");
1761		 *
1762		 * // use a function to randomize the duration for some reason.
1763		 * var durationValueCache;
1764		 * function durationCallback () {
1765		 *   return durationValueCache;
1766		 * }
1767		 * function updateDuration () {
1768		 *   durationValueCache = Math.random() * 100;
1769		 * }
1770		 * updateDuration(); // set to initial value
1771		 * scene.duration(durationCallback); // set duration callback
1772		 *
1773		 * @fires {@link Scene.change}, when used as setter
1774		 * @fires {@link Scene.shift}, when used as setter
1775		 * @param {(number|string|function)} [newDuration] - The new duration setting for the scene.
1776		 * @returns {number} `get` -  Current scene duration.
1777		 * @returns {Scene} `set` -  Parent object for chaining.
1778		 */
1779
1780		/**
1781		 * **Get** or **Set** the offset option value.
1782		 * @method ScrollMagic.Scene#offset
1783		 * @example
1784		 * // get the current offset
1785		 * var offset = scene.offset();
1786		 *
1787		 * // set a new offset
1788		 * scene.offset(100);
1789		 *
1790		 * @fires {@link Scene.change}, when used as setter
1791		 * @fires {@link Scene.shift}, when used as setter
1792		 * @param {number} [newOffset] - The new offset of the scene.
1793		 * @returns {number} `get` -  Current scene offset.
1794		 * @returns {Scene} `set` -  Parent object for chaining.
1795		 */
1796
1797		/**
1798		 * **Get** or **Set** the triggerElement option value.
1799		 * Does **not** fire `Scene.shift`, because changing the trigger Element doesn't necessarily mean the start position changes. This will be determined in `Scene.refresh()`, which is automatically triggered.
1800		 * @method ScrollMagic.Scene#triggerElement
1801		 * @example
1802		 * // get the current triggerElement
1803		 * var triggerElement = scene.triggerElement();
1804		 *
1805		 * // set a new triggerElement using a selector
1806		 * scene.triggerElement("#trigger");
1807		 * // set a new triggerElement using a DOM object
1808		 * scene.triggerElement(document.getElementById("trigger"));
1809		 *
1810		 * @fires {@link Scene.change}, when used as setter
1811		 * @param {(string|object)} [newTriggerElement] - The new trigger element for the scene.
1812		 * @returns {(string|object)} `get` -  Current triggerElement.
1813		 * @returns {Scene} `set` -  Parent object for chaining.
1814		 */
1815
1816		/**
1817		 * **Get** or **Set** the triggerHook option value.
1818		 * @method ScrollMagic.Scene#triggerHook
1819		 * @example
1820		 * // get the current triggerHook value
1821		 * var triggerHook = scene.triggerHook();
1822		 *
1823		 * // set a new triggerHook using a string
1824		 * scene.triggerHook("onLeave");
1825		 * // set a new triggerHook using a number
1826		 * scene.triggerHook(0.7);
1827		 *
1828		 * @fires {@link Scene.change}, when used as setter
1829		 * @fires {@link Scene.shift}, when used as setter
1830		 * @param {(number|string)} [newTriggerHook] - The new triggerHook of the scene. See {@link Scene} parameter description for value options.
1831		 * @returns {number} `get` -  Current triggerHook (ALWAYS numerical).
1832		 * @returns {Scene} `set` -  Parent object for chaining.
1833		 */
1834
1835		/**
1836		 * **Get** or **Set** the reverse option value.
1837		 * @method ScrollMagic.Scene#reverse
1838		 * @example
1839		 * // get the current reverse option
1840		 * var reverse = scene.reverse();
1841		 *
1842		 * // set new reverse option
1843		 * scene.reverse(false);
1844		 *
1845		 * @fires {@link Scene.change}, when used as setter
1846		 * @param {boolean} [newReverse] - The new reverse setting of the scene.
1847		 * @returns {boolean} `get` -  Current reverse option value.
1848		 * @returns {Scene} `set` -  Parent object for chaining.
1849		 */
1850
1851		/**
1852		 * **Get** or **Set** the loglevel option value.
1853		 * @method ScrollMagic.Scene#loglevel
1854		 * @example
1855		 * // get the current loglevel
1856		 * var loglevel = scene.loglevel();
1857		 *
1858		 * // set new loglevel
1859		 * scene.loglevel(3);
1860		 *
1861		 * @fires {@link Scene.change}, when used as setter
1862		 * @param {number} [newLoglevel] - The new loglevel setting of the scene. `[0-3]`
1863		 * @returns {number} `get` -  Current loglevel.
1864		 * @returns {Scene} `set` -  Parent object for chaining.
1865		 */
1866
1867		/**
1868		 * **Get** the associated controller.
1869		 * @method ScrollMagic.Scene#controller
1870		 * @example
1871		 * // get the controller of a scene
1872		 * var controller = scene.controller();
1873		 *
1874		 * @returns {ScrollMagic.Controller} Parent controller or `undefined`
1875		 */
1876		this.controller = function () {
1877			return _controller;
1878		};
1879
1880		/**
1881		 * **Get** the current state.
1882		 * @method ScrollMagic.Scene#state
1883		 * @example
1884		 * // get the current state
1885		 * var state = scene.state();
1886		 *
1887		 * @returns {string} `"BEFORE"`, `"DURING"` or `"AFTER"`
1888		 */
1889		this.state = function () {
1890			return _state;
1891		};
1892
1893		/**
1894		 * **Get** the current scroll offset for the start of the scene.  
1895		 * Mind, that the scrollOffset is related to the size of the container, if `triggerHook` is bigger than `0` (or `"onLeave"`).  
1896		 * This means, that resizing the container or changing the `triggerHook` will influence the scene's start offset.
1897		 * @method ScrollMagic.Scene#scrollOffset
1898		 * @example
1899		 * // get the current scroll offset for the start and end of the scene.
1900		 * var start = scene.scrollOffset();
1901		 * var end = scene.scrollOffset() + scene.duration();
1902		 * console.log("the scene starts at", start, "and ends at", end);
1903		 *
1904		 * @returns {number} The scroll offset (of the container) at which the scene will trigger. Y value for vertical and X value for horizontal scrolls.
1905		 */
1906		this.scrollOffset = function () {
1907			return _scrollOffset.start;
1908		};
1909
1910		/**
1911		 * **Get** the trigger position of the scene (including the value of the `offset` option).  
1912		 * @method ScrollMagic.Scene#triggerPosition
1913		 * @example
1914		 * // get the scene's trigger position
1915		 * var triggerPosition = scene.triggerPosition();
1916		 *
1917		 * @returns {number} Start position of the scene. Top position value for vertical and left position value for horizontal scrolls.
1918		 */
1919		this.triggerPosition = function () {
1920			var pos = _options.offset; // the offset is the basis
1921			if (_controller) {
1922				// get the trigger position
1923				if (_options.triggerElement) {
1924					// Element as trigger
1925					pos += _triggerPos;
1926				} else {
1927					// return the height of the triggerHook to start at the beginning
1928					pos += _controller.info("size") * Scene.triggerHook();
1929				}
1930			}
1931			return pos;
1932		};
1933
1934
1935		var
1936			_pin,
1937			_pinOptions;
1938
1939		Scene
1940			.on("shift.internal", function (e) {
1941				var durationChanged = e.reason === "duration";
1942				if ((_state === SCENE_STATE_AFTER && durationChanged) || (_state === SCENE_STATE_DURING && _options.duration === 0)) {
1943					// if [duration changed after a scene (inside scene progress updates pin position)] or [duration is 0, we are in pin phase and some other value changed].
1944					updatePinState();
1945				}
1946				if (durationChanged) {
1947					updatePinDimensions();
1948				}
1949			})
1950			.on("progress.internal", function (e) {
1951				updatePinState();
1952			})
1953			.on("add.internal", function (e) {
1954				updatePinDimensions();
1955			})
1956			.on("destroy.internal", function (e) {
1957				Scene.removePin(e.reset);
1958			});
1959		/**
1960		 * Update the pin state.
1961		 * @private
1962		 */
1963		var updatePinState = function (forceUnpin) {
1964			if (_pin && _controller) {
1965				var
1966					containerInfo = _controller.info(),
1967					pinTarget = _pinOptions.spacer.firstChild; // may be pin element or another spacer, if cascading pins
1968
1969				if (!forceUnpin && _state === SCENE_STATE_DURING) { // during scene or if duration is 0 and we are past the trigger
1970					// pinned state
1971					if (_util.css(pinTarget, "position") != "fixed") {
1972						// change state before updating pin spacer (position changes due to fixed collapsing might occur.)
1973						_util.css(pinTarget, {
1974							"position": "fixed"
1975						});
1976						// update pin spacer
1977						updatePinDimensions();
1978					}
1979
1980					var
1981						fixedPos = _util.get.offset(_pinOptions.spacer, true), // get viewport position of spacer
1982						scrollDistance = _options.reverse || _options.duration === 0 ?
1983						containerInfo.scrollPos - _scrollOffset.start // quicker
1984						:
1985						Math.round(_progress * _options.duration * 10) / 10; // if no reverse and during pin the position needs to be recalculated using the progress
1986
1987					// add scrollDistance
1988					fixedPos[containerInfo.vertical ? "top" : "left"] += scrollDistance;
1989
1990					// set new values
1991					_util.css(_pinOptions.spacer.firstChild, {
1992						top: fixedPos.top,
1993						left: fixedPos.left
1994					});
1995				} else {
1996					// unpinned state
1997					var
1998						newCSS = {
1999							position: _pinOptions.inFlow ? "relative" : "absolute",
2000							top: 0,
2001							left: 0
2002						},
2003						change = _util.css(pinTarget, "position") != newCSS.position;
2004
2005					if (!_pinOptions.pushFollowers) {
2006						newCSS[containerInfo.vertical ? "top" : "left"] = _options.duration * _progress;
2007					} else if (_options.duration > 0) { // only concerns scenes with duration
2008						if (_state === SCENE_STATE_AFTER && parseFloat(_util.css(_pinOptions.spacer, "padding-top")) === 0) {
2009							change = true; // if in after state but havent updated spacer yet (jumped past pin)
2010						} else if (_state === SCENE_STATE_BEFORE && parseFloat(_util.css(_pinOptions.spacer, "padding-bottom")) === 0) { // before
2011							change = true; // jumped past fixed state upward direction
2012						}
2013					}
2014					// set new values
2015					_util.css(pinTarget, newCSS);
2016					if (change) {
2017						// update pin spacer if state changed
2018						updatePinDimensions();
2019					}
2020				}
2021			}
2022		};
2023
2024		/**
2025		 * Update the pin spacer and/or element size.
2026		 * The size of the spacer needs to be updated whenever the duration of the scene changes, if it is to push down following elements.
2027		 * @private
2028		 */
2029		var updatePinDimensions = function () {
2030			if (_pin && _controller && _pinOptions.inFlow) { // no spacerresize, if original position is absolute
2031				var
2032					after = (_state === SCENE_STATE_AFTER),
2033					before = (_state === SCENE_STATE_BEFORE),
2034					during = (_state === SCENE_STATE_DURING),
2035					vertical = _controller.info("vertical"),
2036					pinTarget = _pinOptions.spacer.firstChild, // usually the pined element but can also be another spacer (cascaded pins)
2037					marginCollapse = _util.isMarginCollapseType(_util.css(_pinOptions.spacer, "display")),
2038					css = {};
2039
2040				// set new size
2041				// if relsize: spacer -> pin | else: pin -> spacer
2042				if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
2043					if (during) {
2044						_util.css(_pin, {
2045							"width": _util.get.width(_pinOptions.spacer)
2046						});
2047					} else {
2048						_util.css(_pin, {
2049							"width": "100%"
2050						});
2051					}
2052				} else {
2053					// minwidth is needed for cascaded pins.
2054					css["min-width"] = _util.get.width(vertical ? _pin : pinTarget, true, true);
2055					css.width = during ? css["min-width"] : "auto";
2056				}
2057				if (_pinOptions.relSize.height) {
2058					if (during) {
2059						// the only padding the spacer should ever include is the duration (if pushFollowers = true), so we need to substract that.
2060						_util.css(_pin, {
2061							"height": _util.get.height(_pinOptions.spacer) - (_pinOptions.pushFollowers ? _options.duration : 0)
2062						});
2063					} else {
2064						_util.css(_pin, {
2065							"height": "100%"
2066						});
2067					}
2068				} else {
2069					// margin is only included if it's a cascaded pin to resolve an IE9 bug
2070					css["min-height"] = _util.get.height(vertical ? pinTarget : _pin, true, !marginCollapse); // needed for cascading pins
2071					css.height = during ? css["min-height"] : "auto";
2072				}
2073
2074				// add space for duration if pushFollowers is true
2075				if (_pinOptions.pushFollowers) {
2076					css["padding" + (vertical ? "Top" : "Left")] = _options.duration * _progress;
2077					css["padding" + (vertical ? "Bottom" : "Right")] = _options.duration * (1 - _progress);
2078				}
2079				_util.css(_pinOptions.spacer, css);
2080			}
2081		};
2082
2083		/**
2084		 * Updates the Pin state (in certain scenarios)
2085		 * If the controller container is not the document and we are mid-pin-phase scrolling or resizing the main document can result to wrong pin positions.
2086		 * So this function is called on resize and scroll of the document.
2087		 * @private
2088		 */
2089		var updatePinInContainer = function () {
2090			if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) {
2091				updatePinState();
2092			}
2093		};
2094
2095		/**
2096		 * Updates the Pin spacer size state (in certain scenarios)
2097		 * If container is resized during pin and relatively sized the size of the pin might need to be updated...
2098		 * So this function is called on resize of the container.
2099		 * @private
2100		 */
2101		var updateRelativePinSpacer = function () {
2102			if (_controller && _pin && // well, duh
2103				_state === SCENE_STATE_DURING && // element in pinned state?
2104				( // is width or height relatively sized, but not in relation to body? then we need to recalc.
2105					((_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) && _util.get.width(window) != _util.get.width(_pinOptions.spacer.parentNode)) ||
2106					(_pinOptions.relSize.height && _util.get.height(window) != _util.get.height(_pinOptions.spacer.parentNode))
2107				)
2108			) {
2109				updatePinDimensions();
2110			}
2111		};
2112
2113		/**
2114		 * Is called, when the mousewhel is used while over a pinned element inside a div container.
2115		 * If the scene is in fixed state scroll events would be counted towards the body. This forwards the event to the scroll container.
2116		 * @private
2117		 */
2118		var onMousewheelOverPin = function (e) {
2119			if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) { // in pin 
2119state
2120				e.preventDefault();
2121				_controller._setScrollPos(_controller.info("scrollPos") - ((e.wheelDelta || e[_controller.info("vertical") ? "wheelDeltaY" : "wheelDeltaX"]) / 3 || -e.detail * 30));
2122			}
2123		};
2124
2125		/**
2126		 * Pin an element for the duration of the scene.
2127		 * If the scene duration is 0 the element will only be unpinned, if the user scrolls back past the start position.  
2128		 * Make sure only one pin is applied to an element at the same time.
2129		 * An element can be pinned multiple times, but only successively.
2130		 * _**NOTE:** The option `pushFollowers` has no effect, when the scene duration is 0._
2131		 * @method ScrollMagic.Scene#setPin
2132		 * @example
2133		 * // pin element and push all following elements down by the amount of the pin duration.
2134		 * scene.setPin("#pin");
2135		 *
2136		 * // pin element and keeping all following elements in their place. The pinned element will move past them.
2137		 * scene.setPin("#pin", {pushFollowers: false});
2138		 *
2139		 * @param {(string|object)} element - A Selector targeting an element or a DOM object that is supposed to be pinned.
2140		 * @param {object} [settings] - settings for the pin
2141		 * @param {boolean} [settings.pushFollowers=true] - If `true` following elements will be "pushed" down for the duration of the pin, if `false` the pinned element will just scroll past them.  
2142		 												   Ignored, when duration is `0`.
2143		 * @param {string} [settings.spacerClass="scrollmagic-pin-spacer"] - Classname of the pin spacer element, which is used to replace the element.
2144		 *
2145		 * @returns {Scene} Parent object for chaining.
2146		 */
2147		this.setPin = function (element, settings) {
2148			var
2149				defaultSettings = {
2150					pushFollowers: true,
2151					spacerClass: "scrollmagic-pin-spacer"
2152				};
2153			var pushFollowersActivelySet = settings && settings.hasOwnProperty('pushFollowers');
2154			settings = _util.extend({}, defaultSettings, settings);
2155
2156			// validate Element
2157			element = _util.get.elements(element)[0];
2158			if (!element) {
2159				log(1, "ERROR calling method 'setPin()': Invalid pin element supplied.");
2160				return Scene; // cancel
2161			} else if (_util.css(element, "position") === "fixed") {
2162				log(1, "ERROR calling method 'setPin()': Pin does not work with elements that are positioned 'fixed'.");
2163				return Scene; // cancel
2164			}
2165
2166			if (_pin) { // preexisting pin?
2167				if (_pin === element) {
2168					// same pin we already have -> do nothing
2169					return Scene; // cancel
2170				} else {
2171					// kill old pin
2172					Scene.removePin();
2173				}
2174
2175			}
2176			_pin = element;
2177
2178			var
2179				parentDisplay = _pin.parentNode.style.display,
2180				boundsParams = ["top", "left", "bottom", "right", "margin", "marginLeft", "marginRight", "marginTop", "marginBottom"];
2181
2182			_pin.parentNode.style.display = 'none'; // hack start to force css to return stylesheet values instead of calculated px values.
2183			var
2184				inFlow = _util.css(_pin, "position") != "absolute",
2185				pinCSS = _util.css(_pin, boundsParams.concat(["display"])),
2186				sizeCSS = _util.css(_pin, ["width", "height"]);
2187			_pin.parentNode.style.display = parentDisplay; // hack end.
2188
2189			if (!inFlow && settings.pushFollowers) {
2190				log(2, "WARNING: If the pinned element is positioned absolutely pushFollowers will be disabled.");
2191				settings.pushFollowers = false;
2192			}
2193			window.setTimeout(function () { // wait until all finished, because with responsive duration it will only be set after scene is added to controller
2194				if (_pin && _options.duration === 0 && pushFollowersActivelySet && settings.pushFollowers) {
2195					log(2, "WARNING: pushFollowers =", true, "has no effect, when scene duration is 0.");
2196				}
2197			}, 0);
2198
2199			// create spacer and insert
2200			var
2201				spacer = _pin.parentNode.insertBefore(document.createElement('div'), _pin),
2202				spacerCSS = _util.extend(pinCSS, {
2203					position: inFlow ? "relative" : "absolute",
2204					boxSizing: "content-box",
2205					mozBoxSizing: "content-box",
2206					webkitBoxSizing: "content-box"
2207				});
2208
2209			if (!inFlow) { // copy size if positioned absolutely, to work for bottom/right positioned elements.
2210				_util.extend(spacerCSS, _util.css(_pin, ["width", "height"]));
2211			}
2212
2213			_util.css(spacer, spacerCSS);
2214			spacer.setAttribute(PIN_SPACER_ATTRIBUTE, "");
2215			_util.addClass(spacer, settings.spacerClass);
2216
2217			// set the pin Options
2218			_pinOptions = {
2219				spacer: spacer,
2220				relSize: { // save if size is defined using % values. if so, handle spacer resize differently...
2221					width: sizeCSS.width.slice(-1) === "%",
2222					height: sizeCSS.height.slice(-1) === "%",
2223					autoFullWidth: sizeCSS.width === "auto" && inFlow && _util.isMarginCollapseType(pinCSS.display)
2224				},
2225				pushFollowers: settings.pushFollowers,
2226				inFlow: inFlow, // stores if the element takes up space in the document flow
2227			};
2228
2229			if (!_pin.___origStyle) {
2230				_pin.___origStyle = {};
2231				var
2232					pinInlineCSS = _pin.style,
2233					copyStyles = boundsParams.concat(["width", "height", "position", "boxSizing", "mozBoxSizing", "webkitBoxSizing"]);
2234				copyStyles.forEach(function (val) {
2235					_pin.___origStyle[val] = pinInlineCSS[val] || "";
2236				});
2237			}
2238
2239			// if relative size, transfer it to spacer and make pin calculate it...
2240			if (_pinOptions.relSize.width) {
2241				_util.css(spacer, {
2242					width: sizeCSS.width
2243				});
2244			}
2245			if (_pinOptions.relSize.height) {
2246				_util.css(spacer, {
2247					height: sizeCSS.height
2248				});
2249			}
2250
2251			// now place the pin element inside the spacer	
2252			spacer.appendChild(_pin);
2253			// and set new css
2254			_util.css(_pin, {
2255				position: inFlow ? "relative" : "absolute",
2256				margin: "auto",
2257				top: "auto",
2258				left: "auto",
2259				bottom: "auto",
2260				right: "auto"
2261			});
2262
2263			if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
2264				_util.css(_pin, {
2265					boxSizing: "border-box",
2266					mozBoxSizing: "border-box",
2267					webkitBoxSizing: "border-box"
2268				});
2269			}
2270
2271			// add listener to document to update pin position in case controller is not the document.
2272			window.addEventListener('scroll', updatePinInContainer);
2273			window.addEventListener('resize', updatePinInContainer);
2274			window.addEventListener('resize', updateRelativePinSpacer);
2275			// add mousewheel listener to catch scrolls over fixed elements
2276			_pin.addEventListener("mousewheel", onMousewheelOverPin);
2277			_pin.addEventListener("DOMMouseScroll", onMousewheelOverPin);
2278
2279			log(3, "added pin");
2280
2281			// finally update the pin to init
2282			updatePinState();
2283
2284			return Scene;
2285		};
2286
2287		/**
2288		 * Remove the pin from the scene.
2289		 * @method ScrollMagic.Scene#removePin
2290		 * @example
2291		 * // remove the pin from the scene without resetting it (the spacer is not removed)
2292		 * scene.removePin();
2293		 *
2294		 * // remove the pin from the scene and reset the pin element to its initial position (spacer is removed)
2295		 * scene.removePin(true);
2296		 *
2297		 * @param {boolean} [reset=false] - If `false` the spacer will not be removed and the element's position will not be reset.
2298		 * @returns {Scene} Parent object for chaining.
2299		 */
2300		this.removePin = function (reset) {
2301			if (_pin) {
2302				if (_state === SCENE_STATE_DURING) {
2303					updatePinState(true); // force unpin at position
2304				}
2305				if (reset || !_controller) { // if there's no controller no progress was made anyway...
2306					var pinTarget = _pinOptions.spacer.firstChild; // usually the pin element, but may be another spacer (cascaded pins)...
2307					if (pinTarget.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // copy margins to child spacer
2308						var
2309							style = _pinOptions.spacer.style,
2310							values = ["margin", "marginLeft", "marginRight", "marginTop", "marginBottom"],
2311							margins = {};
2312						values.forEach(function (val) {
2313							margins[val] = style[val] || "";
2314						});
2315						_util.css(pinTarget, margins);
2316					}
2317					_pinOptions.spacer.parentNode.insertBefore(pinTarget, _pinOptions.spacer);
2318					_pinOptions.spacer.parentNode.removeChild(_pinOptions.spacer);
2319					if (!_pin.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // if it's the last pin for this element -> restore inline styles
2320						// TODO: only correctly set for first pin (when cascading) - how to fix?
2321						_util.css(_pin, _pin.___origStyle);
2322						delete _pin.___origStyle;
2323					}
2324				}
2325				window.removeEventListener('scroll', updatePinInContainer);
2326				window.removeEventListener('resize', updatePinInContainer);
2327				window.removeEventListener('resize', updateRelativePinSpacer);
2328				_pin.removeEventListener("mousewheel", onMousewheelOverPin);
2329				_pin.removeEventListener("DOMMouseScroll", onMousewheelOverPin);
2330				_pin = undefined;
2331				log(3, "removed pin (reset: " + (reset ? "true" : "false") + ")");
2332			}
2333			return Scene;
2334		};
2335
2336
2337		var
2338			_cssClasses,
2339			_cssClassElems = [];
2340
2341		Scene
2342			.on("destroy.internal", function (e) {
2343				Scene.removeClassToggle(e.reset);
2344			});
2345		/**
2346		 * Define a css class modification while the scene is active.  
2347		 * When the scene triggers the classes will be added to the supplied element and removed, when the scene is over.
2348		 * If the scene duration is 0 the classes will only be removed if the user scrolls back past the start position.
2349		 * @method ScrollMagic.Scene#setClassToggle
2350		 * @example
2351		 * // add the class 'myclass' to the element with the id 'my-elem' for the duration of the scene
2352		 * scene.setClassToggle("#my-elem", "myclass");
2353		 *
2354		 * // add multiple classes to multiple elements defined by the selector '.classChange'
2355		 * scene.setClassToggle(".classChange", "class1 class2 class3");
2356		 *
2357		 * @param {(string|object)} element - A Selector targeting one or more elements or a DOM object that is supposed to be modified.
2358		 * @param {string} classes - One or more Classnames (separated by space) that should be added to the element during the scene.
2359		 *
2360		 * @returns {Scene} Parent object for chaining.
2361		 */
2362		this.setClassToggle = function (element, classes) {
2363			var elems = _util.get.elements(element);
2364			if (elems.length === 0 || !_util.type.String(classes)) {
2365				log(1, "ERROR calling method 'setClassToggle()': Invalid " + (elems.length === 0 ? "element" : "classes") + " supplied.");
2366				return Scene;
2367			}
2368			if (_cssClassElems.length > 0) {
2369				// remove old ones
2370				Scene.removeClassToggle();
2371			}
2372			_cssClasses = classes;
2373			_cssClassElems = elems;
2374			Scene.on("enter.internal_class leave.internal_class", function (e) {
2375				var toggle = e.type === "enter" ? _util.addClass : _util.removeClass;
2376				_cssClassElems.forEach(function (elem, key) {
2377					toggle(elem, _cssClasses);
2378				});
2379			});
2380			return Scene;
2381		};
2382
2383		/**
2384		 * Remove the class binding from the scene.
2385		 * @method ScrollMagic.Scene#removeClassToggle
2386		 * @example
2387		 * // remove class binding from the scene without reset
2388		 * scene.removeClassToggle();
2389		 *
2390		 * // remove class binding and remove the changes it caused
2391		 * scene.removeClassToggle(true);
2392		 *
2393		 * @param {boolean} [reset=false] - If `false` and the classes are currently active, they will remain on the element. If `true` they will be removed.
2394		 * @returns {Scene} Parent object for chaining.
2395		 */
2396		this.removeClassToggle = function (reset) {
2397			if (reset) {
2398				_cssClassElems.forEach(function (elem, key) {
2399					_util.removeClass(elem, _cssClasses);
2400				});
2401			}
2402			Scene.off("start.internal_class end.internal_class");
2403			_cssClasses = undefined;
2404			_cssClassElems = [];
2405			return Scene;
2406		};
2407
2408		// INIT
2409		construct();
2410		return Scene;
2411	};
2412
2413	// store pagewide scene options
2414	var SCENE_OPTIONS = {
2415		defaults: {
2416			duration: 0,
2417			offset: 0,
2418			triggerElement: undefined,
2419			triggerHook: 0.5,
2420			reverse: true,
2421			loglevel: 2
2422		},
2423		validate: {
2424			offset: function (val) {
2425				val = parseFloat(val);
2426				if (!_util.type.Number(val)) {
2427					throw ["Invalid value for option \"offset\":", val];
2428				}
2429				return val;
2430			},
2431			triggerElement: function (val) {
2432				val = val || undefined;
2433				if (val) {
2434					var elem = _util.get.elements(val)[0];
2435					if (elem && elem.parentNode) {
2436						val = elem;
2437					} else {
2438						throw ["Element defined in option \"triggerElement\" was not found:", val];
2439					}
2440				}
2441				return val;
2442			},
2443			triggerHook: function (val) {
2444				var translate = {
2445					"onCenter": 0.5,
2446					"onEnter": 1,
2447					"onLeave": 0
2448				};
2449				if (_util.type.Number(val)) {
2450					val = Math.max(0, Math.min(parseFloat(val), 1)); //  make sure its betweeen 0 and 1
2451				} else if (val in translate) {
2452					val = translate[val];
2453				} else {
2454					throw ["Invalid value for option \"triggerHook\": ", val];
2455				}
2456				return val;
2457			},
2458			reverse: function (val) {
2459				return !!val; // force boolean
2460			},
2461			loglevel: function (val) {
2462				val = parseInt(val);
2463				if (!_util.type.Number(val) || val < 0 || val > 3) {
2464					throw ["Invalid value for option \"loglevel\":", val];
2465				}
2466				return val;
2467			}
2468		}, // holder for  validation methods. duration validation is handled in 'getters-setters.js'
2469		shifts: ["duration", "offset", "triggerHook"], // list of options that trigger a `shift` event
2470	};
2471	/*
2472	 * method used to add an option to ScrollMagic Scenes.
2473	 * TODO: DOC (private for dev)
2474	 */
2475	ScrollMagic.Scene.addOption = function (name, defaultValue, validationCallback, shifts) {
2476		if (!(name in SCENE_OPTIONS.defaults)) {
2477			SCENE_OPTIONS.defaults[name] = defaultValue;
2478			SCENE_OPTIONS.validate[name] = validationCallback;
2479			if (shifts) {
2480				SCENE_OPTIONS.shifts.push(name);
2481			}
2482		} else {
2483			ScrollMagic._util.log(1, "[static] ScrollMagic.Scene -> Cannot add Scene option '" + name + "', because it already exists.");
2484		}
2485	};
2486	// instance extension function for plugins
2487	// TODO: DOC (private for dev)
2488	ScrollMagic.Scene.extend = function (extension) {
2489		var oldClass = this;
2490		ScrollMagic.Scene = function () {
2491			oldClass.apply(this, arguments);
2492			this.$super = _util.extend({}, this); // copy parent state
2493			return extension.apply(this, arguments) || this;
2494		};
2495		_util.extend(ScrollMagic.Scene, oldClass); // copy properties
2496		ScrollMagic.Scene.prototype = oldClass.prototype; // copy prototype
2497		ScrollMagic.Scene.prototype.constructor = ScrollMagic.Scene; // restore constructor
2498	};
2499
2500
2501
2502	/**
2503	 * TODO: DOCS (private for dev)
2504	 * @class
2505	 * @private
2506	 */
2507
2508	ScrollMagic.Event = function (type, namespace, target, vars) {
2509		vars = vars || {};
2510		for (var key in vars) {
2511			this[key] = vars[key];
2512		}
2513		this.type = type;
2514		this.target = this.currentTarget = target;
2515		this.namespace = namespace || '';
2516		this.timeStamp = this.timestamp = Date.now();
2517		return this;
2518	};
2519
2520	/*
2521	 * TODO: DOCS (private for dev)
2522	 */
2523
2524	var _util = ScrollMagic._util = (function (window) {
2525		var U = {},
2526			i;
2527
2528		/**
2529		 * ------------------------------
2530		 * internal helpers
2531		 * ------------------------------
2532		 */
2533
2534		// parse float and fall back to 0.
2535		var floatval = function (number) {
2536			return parseFloat(number) || 0;
2537		};
2538		// get current style IE safe (otherwise IE would return calculated values for 'auto')
2539		var _getComputedStyle = function (elem) {
2540			return elem.currentStyle ? elem.currentStyle : window.getComputedStyle(elem);
2541		};
2542
2543		// get element dimension (width or height)
2544		var _dimension = function (which, elem, outer, includeMargin) {
2545			elem = (elem === document) ? window : elem;
2546			if (elem === window) {
2547				includeMargin = false;
2548			} else if (!_type.DomElement(elem)) {
2549				return 0;
2550			}
2551			which = which.charAt(0).toUpperCase() + which.substr(1).toLowerCase();
2552			var dimension = (outer ? elem['offset' + which] || elem['outer' + which] : elem['client' + which] || elem['inner' + which]) || 0;
2553			if (outer && includeMargin) {
2554				var style = _getComputedStyle(elem);
2555				dimension += which === 'Height' ? floatval(style.marginTop) + floatval(style.marginBottom) : floatval(style.marginLeft) + floatval(style.marginRight);
2556			}
2557			return dimension;
2558		};
2559		// converts 'margin-top' into 'marginTop'
2560		var _camelCase = function (str) {
2561			return str.replace(/^[^a-z]+([a-z])/g, '$1').replace(/-([a-z])/g, function (g) {
2562				return g[1].toUpperCase();
2563			});
2564		};
2565
2566		/**
2567		 * ------------------------------
2568		 * external helpers
2569		 * ------------------------------
2570		 */
2571
2572		// extend obj – same as jQuery.extend({}, objA, objB)
2573		U.extend = function (obj) {
2574			obj = obj || {};
2575			for (i = 1; i < arguments.length; i++) {
2576				if (!arguments[i]) {
2577					continue;
2578				}
2579				for (var key in arguments[i]) {
2580					if (arguments[i].hasOwnProperty(key)) {
2581						obj[key] = arguments[i][key];
2582					}
2583				}
2584			}
2585			return obj;
2586		};
2587
2588		// check if a css display type results in margin-collapse or not
2589		U.isMarginCollapseType = function (str) {
2590			return ["block", "flex", "list-item", "table", "-webkit-box"].indexOf(str) > -1;
2591		};
2592
2593		// implementation of requestAnimationFrame
2594		// based on https://gist.github.com/paulirish/1579671
2595		var
2596			lastTime = 0,
2597			vendors = ['ms', 'moz', 'webkit', 'o'];
2598		var _requestAnimationFrame = window.requestAnimationFrame;
2599		var _cancelAnimationFrame = window.cancelAnimationFrame;
2600		// try vendor prefixes if the above doesn't work
2601		for (i = 0; !_requestAnimationFrame && i < vendors.length; ++i) {
2602			_requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
2603			_cancelAnimationFrame = window[vendors[i] + 'CancelAnimationFrame'] || window[vendors[i] + 'CancelRequestAnimationFrame'];
2604		}
2605
2606		// fallbacks
2607		if (!_requestAnimationFrame) {
2608			_requestAnimationFrame = function (callback) {
2609				var
2610					currTime = new Date().getTime(),
2611					timeToCall = Math.max(0, 16 - (currTime - lastTime)),
2612					id = window.setTimeout(function () {
2613						callback(currTime + timeToCall);
2614					}, timeToCall);
2615				lastTime = currTime + timeToCall;
2616				return id;
2617			};
2618		}
2619		if (!_cancelAnimationFrame) {
2620			_cancelAnimationFrame = function (id) {
2621				window.clearTimeout(id);
2622			};
2623		}
2624		U.rAF = _requestAnimationFrame.bind(window);
2625		U.cAF = _cancelAnimationFrame.bind(window);
2626
2627		var
2628			loglevels = ["error", "warn", "log"],
2629			console = window.console || {};
2630
2631		console.log = console.log || function () {}; // no console log, well - do nothing then...
2632		// make sure methods for all levels exist.
2633		for (i = 0; i < loglevels.length; i++) {
2634			var method = loglevels[i];
2635			if (!console[method]) {
2636				console[method] = console.log; // prefer .log over nothing
2637			}
2638		}
2639		U.log = function (loglevel) {
2640			if (loglevel > loglevels.length || loglevel <= 0) loglevel = loglevels.length;
2641			var now = new Date(),
2642				time = ("0" + now.getHours()).slice(-2) + ":" + ("0" + now.getMinutes()).slice(-2) + ":" + ("0" + now.getSeconds()).slice(-2) + ":" + ("00" + now.getMilliseconds()).slice(-3),
2643				method = loglevels[loglevel - 1],
2644				args = Array.prototype.splice.call(arguments, 1),
2645				func = Function.prototype.bind.call(console[method], console);
2646			args.unshift(time);
2647			func.apply(console, args);
2648		};
2649
2650		/**
2651		 * ------------------------------
2652		 * type testing
2653		 * ------------------------------
2654		 */
2655
2656		var _type = U.type = function (v) {
2657			return Object.prototype.toString.call(v).replace(/^\[object (.+)\]$/, "$1").toLowerCase();
2658		};
2659		_type.String = function (v) {
2660			return _type(v) === 'string';
2661		};
2662		_type.Function = function (v) {
2663			return _type(v) === 'function';
2664		};
2665		_type.Array = function (v) {
2666			return Array.isArray(v);
2667		};
2668		_type.Number = function (v) {
2669			return !_type.Array(v) && (v - parseFloat(v) + 1) >= 0;
2670		};
2671		_type.DomElement = function (o) {
2672			return (
2673				typeof HTMLElement === "object" || typeof HTMLElement === "function" ? o instanceof HTMLElement || o instanceof SVGElement : //DOM2
2674				o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
2675			);
2676		};
2677
2678		/**
2679		 * ------------------------------
2680		 * DOM Element info
2681		 * ------------------------------
2682		 */
2683		// always returns a list of matching DOM elements, from a selector, a DOM element or an list of elements or even an array of selectors
2684		var _get = U.get = {};
2685		_get.elements = function (selector) {
2686			var arr = [];
2687			if (_type.String(selector)) {
2688				try {
2689					selector = document.querySelectorAll(selector);
2690				} catch (e) { // invalid selector
2691					return arr;
2692				}
2693			}
2694			if (_type(selector) === 'nodelist' || _type.Array(selector) || selector instanceof NodeList) {
2695				for (var i = 0, ref = arr.length = selector.length; i < ref; i++) { // list of elements
2696					var elem = selector[i];
2697					arr[i] = _type.DomElement(elem) ? elem : _get.elements(elem); // if not an element, try to resolve recursively
2698				}
2699			} else if (_type.DomElement(selector) || selector === document || selector === window) {
2700				arr = [selector]; // only the element
2701			}
2702			return arr;
2703		};
2704		// get scroll top value
2705		_get.scrollTop = function (elem) {
2706			return (elem && typeof elem.scrollTop === 'number') ? elem.scrollTop : window.pageYOffset || 0;
2707		};
2708		// get scroll left value
2709		_get.scrollLeft = function (elem) {
2710			return (elem && typeof elem.scrollLeft === 'number') ? elem.scrollLeft : window.pageXOffset || 0;
2711		};
2712		// get element height
2713		_get.width = function (elem, outer, includeMargin) {
2714			return _dimension('width', elem, outer, includeMargin);
2715		};
2716		// get element width
2717		_get.height = function (elem, outer, includeMargin) {
2718			return _dimension('height', elem, outer, includeMargin);
2719		};
2720
2721		// get element position (optionally relative to viewport)
2722		_get.offset = function (elem, relativeToViewport) {
2723			var offset = {
2724				top: 0,
2725				left: 0
2726			};
2727			if (elem && elem.getBoundingClientRect) { // check if available
2728				var rect = elem.getBoundingClientRect();
2729				offset.top = rect.top;
2730				offset.left = rect.left;
2731				if (!relativeToViewport) { // clientRect is by default relative to viewport...
2732					offset.top += _get.scrollTop();
2733					offset.left += _get.scrollLeft();
2734				}
2735			}
2736			return offset;
2737		};
2738
2739		/**
2740		 * ------------------------------
2741		 * DOM Element manipulation
2742		 * ------------------------------
2743		 */
2744
2745		U.addClass = function (elem, classname) {
2746			if (classname) {
2747				if (elem.classList)
2748					elem.classList.add(classname);
2749				else
2750					elem.className += ' ' + classname;
2751			}
2752		};
2753		U.removeClass = function (elem, classname) {
2754			if (classname) {
2755				if (elem.classList)
2756					elem.classList.remove(classname);
2757				else
2758					elem.className = elem.className.replace(new RegExp('(^|\\b)' + classname.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
2759			}
2760		};
2761		// if options is string -> returns css value
2762		// if options is array -> returns object with css value pairs
2763		// if options is object -> set new css values
2764		U.css = function (elem, options) {
2765			if (_type.String(options)) {
2766				return _getComputedStyle(elem)[_camelCase(options)];
2767			} else if (_type.Array(options)) {
2768				var
2769					obj = {},
2770					style = _getComputedStyle(elem);
2771				options.forEach(function (option, key) {
2772					obj[option] = style[_camelCase(option)];
2773				});
2774				return obj;
2775			} else {
2776				for (var option in options) {
2777					var val = options[option];
2778					if (val == parseFloat(val)) { // assume pixel for seemingly numerical values
2779						val += 'px';
2780					}
2781					elem.style[_camelCase(option)] = val;
2782				}
2783			}
2784		};
2785
2786		return U;
2787	}(window || {}));
2788
2789
2790	ScrollMagic.Scene.prototype.addIndicators = function () {
2791		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling addIndicators() due to missing Plugin \'debug.addIndicators\'. Please make sure to include plugins/debug.addIndicators.js');
2792		return this;
2793	}
2794	ScrollMagic.Scene.prototype.removeIndicators = function () {
2795		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeIndicators() due to missing Plugin \'debug.addIndicators\'. Please make sure to include plugins/debug.addIndicators.js');
2796		return this;
2797	}
2798	ScrollMagic.Scene.prototype.setTween = function () {
2799		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling setTween() due to missing Plugin \'animation.gsap\'. Please make sure to include plugins/animation.gsap.js');
2800		return this;
2801	}
2802	ScrollMagic.Scene.prototype.removeTween = function () {
2803		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeTween() due to missing Plugin \'animation.gsap\'. Please make sure to include plugins/animation.gsap.js');
2804		return this;
2805	}
2806	ScrollMagic.Scene.prototype.setVelocity = function () {
2807		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling setVelocity() due to missing Plugin \'animation.velocity\'. Please make sure to include plugins/animation.velocity.js');
2808		return this;
2809	}
2810	ScrollMagic.Scene.prototype.removeVelocity = function () {
2811		ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeVelocity() due to missing Plugin \'animation.velocity\'. Please make sure to include plugins/animation.velocity.js');
2812		return this;
2813	}
2814
2815	return ScrollMagic;
2816}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.