# Source: ScrollMagic/Scene/feature-pinning.js

    var
    	_pin,
    	_pinOptions;

    Scene
    	.on("shift.internal", function (e) {
    		var durationChanged = e.reason === "duration";
    		if ((_state === SCENE_STATE_AFTER && durationChanged) || (_state === SCENE_STATE_DURING && _options.duration === 0)) {
    			// 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].
    			updatePinState();
    		}
    		if (durationChanged) {
    			updatePinDimensions();
    		}
    	})
    	.on("progress.internal", function (e) {
    		updatePinState();
    	})
    	.on("add.internal", function (e) {
    		updatePinDimensions();
    	})
    	.on("destroy.internal", function (e) {
    		Scene.removePin(e.reset);
    	});
    /**
     * Update the pin state.
     * @private
     */
    var updatePinState = function (forceUnpin) {
    	if (_pin && _controller) {
    		var
    			containerInfo = _controller.info(),
    			pinTarget = _pinOptions.spacer.firstChild; // may be pin element or another spacer, if cascading pins

    		if (!forceUnpin && _state === SCENE_STATE_DURING) { // during scene or if duration is 0 and we are past the trigger
    			// pinned state
    			if (_util.css(pinTarget, "position") != "fixed") {
    				// change state before updating pin spacer (position changes due to fixed collapsing might occur.)
    				_util.css(pinTarget, {"position": "fixed"});
    				// update pin spacer
    				updatePinDimensions();
    			}

    			var
    				fixedPos = _util.get.offset(_pinOptions.spacer, true), // get viewport position of spacer
    				scrollDistance = _options.reverse || _options.duration === 0 ?
    								 	 containerInfo.scrollPos - _scrollOffset.start // quicker
    								 : Math.round(_progress * _options.duration * 10)/10; // if no reverse and during pin the position needs to be recalculated using the progress

    			// add scrollDistance
    			fixedPos[containerInfo.vertical ? "top" : "left"] += scrollDistance;

    			// set new values
    			_util.css(_pinOptions.spacer.firstChild, {
    				top: fixedPos.top,
    				left: fixedPos.left
    			});
    		} else {
    			// unpinned state
    			var
    				newCSS = {
    					position: _pinOptions.inFlow ? "relative" : "absolute",
    					top:  0,
    					left: 0
    				},
    				change = _util.css(pinTarget, "position") != newCSS.position;

    			if (!_pinOptions.pushFollowers) {
    				newCSS[containerInfo.vertical ? "top" : "left"] = _options.duration * _progress;
    			} else if (_options.duration > 0) { // only concerns scenes with duration
    				if (_state === SCENE_STATE_AFTER && parseFloat(_util.css(_pinOptions.spacer, "padding-top")) === 0) {
    					change = true; // if in after state but havent updated spacer yet (jumped past pin)
    				} else if (_state === SCENE_STATE_BEFORE && parseFloat(_util.css(_pinOptions.spacer, "padding-bottom")) === 0) { // before
    					change = true; // jumped past fixed state upward direction
    				}
    			}
    			// set new values
    			_util.css(pinTarget, newCSS);
    			if (change) {
    				// update pin spacer if state changed
    				updatePinDimensions();
    			}
    		}
    	}
    };

    /**
     * Update the pin spacer and/or element size.
     * The size of the spacer needs to be updated whenever the duration of the scene changes, if it is to push down following elements.
     * @private
     */
    var updatePinDimensions = function () {
    	if (_pin && _controller && _pinOptions.inFlow) { // no spacerresize, if original position is absolute
    		var
    			after = (_state === SCENE_STATE_AFTER),
    			before = (_state === SCENE_STATE_BEFORE),
    			during = (_state === SCENE_STATE_DURING),
    			vertical = _controller.info("vertical"),
    			pinTarget = _pinOptions.spacer.firstChild, // usually the pined element but can also be another spacer (cascaded pins)
    			marginCollapse = _util.isMarginCollapseType(_util.css(_pinOptions.spacer, "display")),
    			css = {};

    		// set new size
    		// if relsize: spacer -> pin | else: pin -> spacer
    		if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
    			if (during) {
    				_util.css(_pin, {"width": _util.get.width(_pinOptions.spacer)});
    			} else {
    				_util.css(_pin, {"width": "100%"});
    			}
    		} else {
    			// minwidth is needed for cascaded pins.
    			css["min-width"] = _util.get.width(vertical ? _pin : pinTarget, true, true);
    			css.width = during ? css["min-width"] : "auto";
    		}
    		if (_pinOptions.relSize.height) {
    			if (during) {
    				// the only padding the spacer should ever include is the duration (if pushFollowers = true), so we need to substract that.
    				_util.css(_pin, {"height": _util.get.height(_pinOptions.spacer) - (_pinOptions.pushFollowers ? _options.duration : 0)});
    			} else {
    				_util.css(_pin, {"height": "100%"});
    			}
    		} else {
    			// margin is only included if it's a cascaded pin to resolve an IE9 bug
    			css["min-height"] = _util.get.height(vertical ? pinTarget : _pin, true , !marginCollapse); // needed for cascading pins
    			css.height = during ? css["min-height"] : "auto";
    		}

    		// add space for duration if pushFollowers is true
    		if (_pinOptions.pushFollowers) {
    			css["padding" + (vertical ? "Top" : "Left")] = _options.duration * _progress;
    			css["padding" + (vertical ? "Bottom" : "Right")] = _options.duration * (1 - _progress);
    		}
    		_util.css(_pinOptions.spacer, css);
    	}
    };

    /**
     * Updates the Pin state (in certain scenarios)
     * 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.
     * So this function is called on resize and scroll of the document.
     * @private
     */
    var updatePinInContainer = function () {
    	if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) {
    		updatePinState();
    	}
    };

    /**
     * Updates the Pin spacer size state (in certain scenarios)
     * If container is resized during pin and relatively sized the size of the pin might need to be updated...
     * So this function is called on resize of the container.
     * @private
     */
    var updateRelativePinSpacer = function () {
    	if ( _controller && _pin && // well, duh
    			_state === SCENE_STATE_DURING && // element in pinned state?
    			( // is width or height relatively sized, but not in relation to body? then we need to recalc.
    				((_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) && _util.get.width(window) != _util.get.width(_pinOptions.spacer.parentNode)) ||
    				(_pinOptions.relSize.height && _util.get.height(window) != _util.get.height(_pinOptions.spacer.parentNode))
    			)
    	) {
    		updatePinDimensions();
    	}
    };

    /**
     * Is called, when the mousewhel is used while over a pinned element inside a div container.
     * If the scene is in fixed state scroll events would be counted towards the body. This forwards the event to the scroll container.
     * @private
     */
    var onMousewheelOverPin = function (e) {
    	if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) { // in pin state
    		e.preventDefault();
    		_controller._setScrollPos(_controller.info("scrollPos") - ((e.wheelDelta || e[_controller.info("vertical") ? "wheelDeltaY" : "wheelDeltaX"])/3 || -e.detail*30));
    	}
    };

    /**
     * Pin an element for the duration of the scene.
     * If the scene duration is 0 the element will only be unpinned, if the user scrolls back past the start position.
     * Make sure only one pin is applied to an element at the same time.
     * An element can be pinned multiple times, but only successively.
     * _**NOTE:** The option `pushFollowers` has no effect, when the scene duration is 0._
     * @method ScrollMagic.Scene#setPin
     * @example
     * // pin element and push all following elements down by the amount of the pin duration.
     * scene.setPin("#pin");
     *
     * // pin element and keeping all following elements in their place. The pinned element will move past them.
     * scene.setPin("#pin", {pushFollowers: false});
     *
     * @param {(string|object)} element - A Selector targeting an element or a DOM object that is supposed to be pinned.
     * @param {object} [settings] - settings for the pin
     * @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.
     												   Ignored, when duration is `0`.
     * @param {string} [settings.spacerClass="scrollmagic-pin-spacer"] - Classname of the pin spacer element, which is used to replace the element.
     *
     * @returns {Scene} Parent object for chaining.
     */
    this.setPin = function (element, settings) {
    	var
    		defaultSettings = {
    			pushFollowers: true,
    			spacerClass: "scrollmagic-pin-spacer"
    		};
    	// (BUILD) - REMOVE IN MINIFY - START
    	var pushFollowersActivelySet = settings && settings.hasOwnProperty('pushFollowers');
    	// (BUILD) - REMOVE IN MINIFY - END
    	settings = _util.extend({}, defaultSettings, settings);

    	// validate Element
    	element = _util.get.elements(element)[0];
    	if (!element) {
    		log(1, "ERROR calling method 'setPin()': Invalid pin element supplied.");
    		return Scene; // cancel
    	} else if (_util.css(element, "position") === "fixed") {
    		log(1, "ERROR calling method 'setPin()': Pin does not work with elements that are positioned 'fixed'.");
    		return Scene; // cancel
    	}

    	if (_pin) { // preexisting pin?
    		if (_pin === element) {
    			// same pin we already have -> do nothing
    			return Scene; // cancel
    		} else {
    			// kill old pin
    			Scene.removePin();
    		}

    	}
    	_pin = element;

    	var
    		parentDisplay = _pin.parentNode.style.display,
    		boundsParams = ["top", "left", "bottom", "right", "margin", "marginLeft", "marginRight", "marginTop", "marginBottom"];

    	_pin.parentNode.style.display = 'none'; // hack start to force css to return stylesheet values instead of calculated px values.
    	var
    		inFlow = _util.css(_pin, "position") != "absolute",
    		pinCSS = _util.css(_pin, boundsParams.concat(["display"])),
    		sizeCSS = _util.css(_pin, ["width", "height"]);
    	_pin.parentNode.style.display = parentDisplay; // hack end.

    	if (!inFlow && settings.pushFollowers) {
    		log(2, "WARNING: If the pinned element is positioned absolutely pushFollowers will be disabled.");
    		settings.pushFollowers = false;
    	}
    	// (BUILD) - REMOVE IN MINIFY - START
    	window.setTimeout(function () { // wait until all finished, because with responsive duration it will only be set after scene is added to controller
    		if (_pin && _options.duration === 0 && pushFollowersActivelySet && settings.pushFollowers) {
    			log(2, "WARNING: pushFollowers =", true, "has no effect, when scene duration is 0.");
    		}
    	}, 0);
    	// (BUILD) - REMOVE IN MINIFY - END

    	// create spacer and insert
    	var
    		spacer = _pin.parentNode.insertBefore(document.createElement('div'), _pin),
    		spacerCSS = _util.extend(pinCSS, {
    				position: inFlow ? "relative" : "absolute",
    				boxSizing: "content-box",
    				mozBoxSizing: "content-box",
    				webkitBoxSizing: "content-box"
    			});

    	if (!inFlow) { // copy size if positioned absolutely, to work for bottom/right positioned elements.
    		_util.extend(spacerCSS, _util.css(_pin, ["width", "height"]));
    	}

    	_util.css(spacer, spacerCSS);
    	spacer.setAttribute(PIN_SPACER_ATTRIBUTE, "");
    	_util.addClass(spacer, settings.spacerClass);

    	// set the pin Options
    	_pinOptions = {
    		spacer: spacer,
    		relSize: { // save if size is defined using % values. if so, handle spacer resize differently...
    			width: sizeCSS.width.slice(-1) === "%",
    			height: sizeCSS.height.slice(-1) === "%",
    			autoFullWidth: sizeCSS.width === "auto" && inFlow && _util.isMarginCollapseType(pinCSS.display)
    		},
    		pushFollowers: settings.pushFollowers,
    		inFlow: inFlow, // stores if the element takes up space in the document flow
    	};

    	if (!_pin.___origStyle) {
    		_pin.___origStyle = {};
    		var
    			pinInlineCSS = _pin.style,
    			copyStyles = boundsParams.concat(["width", "height", "position", "boxSizing", "mozBoxSizing", "webkitBoxSizing"]);
    		copyStyles.forEach(function (val) {
    			_pin.___origStyle[val] = pinInlineCSS[val] || "";
    		});
    	}

    	// if relative size, transfer it to spacer and make pin calculate it...
    	if (_pinOptions.relSize.width) {
    		_util.css(spacer, {width: sizeCSS.width});
    	}
    	if (_pinOptions.relSize.height) {
    		_util.css(spacer, {height: sizeCSS.height});
    	}

    	// now place the pin element inside the spacer
    	spacer.appendChild(_pin);
    	// and set new css
    	_util.css(_pin, {
    		position: inFlow ? "relative" : "absolute",
    		margin: "auto",
    		top: "auto",
    		left: "auto",
    		bottom: "auto",
    		right: "auto"
    	});

    	if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
    		_util.css(_pin, {
    			boxSizing : "border-box",
    			mozBoxSizing : "border-box",
    			webkitBoxSizing : "border-box"
    		});
    	}

    	// add listener to document to update pin position in case controller is not the document.
    	window.addEventListener('scroll', updatePinInContainer);
    	window.addEventListener('resize', updatePinInContainer);
    	window.addEventListener('resize', updateRelativePinSpacer);
    	// add mousewheel listener to catch scrolls over fixed elements
    	_pin.addEventListener("mousewheel", onMousewheelOverPin);
    	_pin.addEventListener("DOMMouseScroll", onMousewheelOverPin);

    	log(3, "added pin");

    	// finally update the pin to init
    	updatePinState();

    	return Scene;
    };

    /**
     * Remove the pin from the scene.
     * @method ScrollMagic.Scene#removePin
     * @example
     * // remove the pin from the scene without resetting it (the spacer is not removed)
     * scene.removePin();
     *
     * // remove the pin from the scene and reset the pin element to its initial position (spacer is removed)
     * scene.removePin(true);
     *
     * @param {boolean} [reset=false] - If `false` the spacer will not be removed and the element's position will not be reset.
     * @returns {Scene} Parent object for chaining.
     */
    this.removePin = function (reset) {
    	if (_pin) {
    		if (_state === SCENE_STATE_DURING) {
    			updatePinState(true); // force unpin at position
    		}
    		if (reset || !_controller) { // if there's no controller no progress was made anyway...
    			var pinTarget = _pinOptions.spacer.firstChild; // usually the pin element, but may be another spacer (cascaded pins)...
    			if (pinTarget.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // copy margins to child spacer
    				var
    					style = _pinOptions.spacer.style,
    					values = ["margin", "marginLeft", "marginRight", "marginTop", "marginBottom"],
    					margins = {};
    				values.forEach(function (val) {
    					margins[val] = style[val] || "";
    				});
    				_util.css(pinTarget, margins);
    			}
    			_pinOptions.spacer.parentNode.insertBefore(pinTarget, _pinOptions.spacer);
    			_pinOptions.spacer.parentNode.removeChild(_pinOptions.spacer);
    			if (!_pin.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // if it's the last pin for this element -> restore inline styles
    				// TODO: only correctly set for first pin (when cascading) - how to fix?
    				_util.css(_pin, _pin.___origStyle);
    				delete _pin.___origStyle;
    			}
    		}
    		window.removeEventListener('scroll', updatePinInContainer);
    		window.removeEventListener('resize', updatePinInContainer);
    		window.removeEventListener('resize', updateRelativePinSpacer);
    		_pin.removeEventListener("mousewheel", onMousewheelOverPin);
    		_pin.removeEventListener("DOMMouseScroll", onMousewheelOverPin);
    		_pin = undefined;
    		log(3, "removed pin (reset: " + (reset ? "true" : "false") + ")");
    	}
    	return Scene;
    };