PageSourceSearch

https://www.traubenshow.de/modules/mod_vertical_menu/js/mod_vertical_menu.js?v=4.3.60

js traubenshow.de collected 2026-10-03 00:01:26 UTC 57,735 bytes, 1,561 lines download raw bytes

1/*!
2 * mod_vertical_menu - Vertical Menu
3 *
4 * @author    Balint Polgarfi
5 * @copyright 2014-2026 Offlajn.com - All Rights Reserved
6 * @license   https://gnu.org/licenses/gpl-2.0.html
7 * @link      https://offlajn.com
8 */
9
10;($ => {
11
12	const Hlpr = {
13		width: $.fn.width,
14		hackWidth(reduce) {
15			return function (arg) {
16				var value = Hlpr.width.apply(this, arguments);
17				if (this[0] == window && !$.isNumeric(arg) && value > 767) value -= reduce;
18				return value;
19			}
20		},
21		cancelTransitionEnd(e) {
22			if (e.target.tagName != "NAV") e.stopPropagation();
23		},
24		loadImage(src) {
25			new Image().src = src;
26		},
27		calcOrigin(origin, w, dir) {
28			var o = origin.split(/\s+/);
29			if (o[2] && o[2].indexOf("%") > 0) // convert originZ to px
30				o[2] = parseFloat(o[2]) / 100 * w + "px";
31			if (dir < 0) { // invert
32				if (o[0].indexOf("%") < 0)
33					o[0] = (parseInt(o[0]) / w * 100).toPrecision(3);
34				o[0] = (parseInt(o[0]) - 50) * dir + 50 + "%";
35			}
36			return o.join(" ");
37		},
38		get scrollWidth() {
39			if (this._scrollWidth !== undefined) {
40				return this._scrollWidth;
41			}
42			const div = document.createElement("div");
43			div.style.overflow = "scroll";
44			div.style.width = "100px";
45			div.style.height = "100px";
46			document.body.appendChild(div);
47			this._scrollWidth = div.offsetWidth - div.clientWidth;
48			document.body.removeChild(div);
49
50			return this._scrollWidth;
51		},
52	};
53
54	const Target = {
55		hide() {
56			this.targets()[0].style.display = "none";
57		},
58		remove() {
59			const target = this.targets()[0];
60			target.parentNode.removeChild(target);
61		},
62		scrollUp() {
63			const target = this.targets()[0];
64			$(target.children[0]).scrollTop(0);
65			target.style.display = "none";
66		},
67		autoMaxWidth() {
68			const target = this.targets()[0];
69			target.attributes["class"].value = "sm-title";
70			target.style.maxWidth = "";
71		},
72		autoHeight() {
73			this.targets()[0].style.height = "";
74		}
75	};
76
77	var $body,
78		$win = $(window),
79		$html = $(document.documentElement),
80		backScale = 0.75;
81	if (smMobile) $html.addClass('sm-mobile');
82
83	$.fn._center = function (bi) {
84		var x, w, prev;
85		if (bi) {
86			prev = this[0].parentNode.children[1] == this[0] ? 0 : $(this[0].parentNode.children[0]).outerWidth();
87		} else {
88			prev = this.prev().outerWidth() * backScale || 0;
89		}
90		w = this.css("maxWidth", "none").width() + 30;
91		this[0].style.maxWidth = "";
92		x = Math.floor((this.parent().outerWidth() - w) / 2);
93
94		return prev > x ? prev : x;
95	};
96
97	function deleteMediaRules(match) {
98		var css = '';
99		for (var j = 0; j < document.styleSheets.length; j++) {
100			var style = document.styleSheets[j];
101			if (!style.href || style.href.indexOf('mod_vertical_menu') < 0) try {
102				for (var i = 0; i < style.cssRules.length; i++) {
103					var rule = style.cssRules[i];
104					if (rule.media && rule.media.mediaText.match(match)) {
105						if (style.href)
106							css += rule.cssText.replace(/(url\(['"]?)([^)]+)/i, function (m, url, href) {
107								return href.match(/^\/|^https?:|^data:/i) ? m : url + style.href.replace(/[^/]+$/, '') + href;
108							}) + '\n';
109						else css += rule.cssText + '\n';
110						style.deleteRule(i--);
111					}
112				}
113			} catch (ex) { }
114		}
115		return css;
116	}
117	function hackMediaWidths(width) {
118		width = width || 0;
119		if (!hackMediaWidths.hasOwnProperty('css')) {
120			hackMediaWidths.css = [deleteMediaRules('width')];
121			hackMediaWidths.$css = $('<style id="hack-media">').appendTo(document.head);
122		}
123		if (!hackMediaWidths.css.hasOwnProperty(width)) {
124			hackMediaWidths.css[width] = hackMediaWidths.css[0].replace(/\((\w+-width:\s*)(\d+)px\)/g, function () {
125				return '(' + arguments[1] + (parseInt(arguments[2]) + width) + 'px)';
126			});
127		}
128		hackMediaWidths.$css.html(hackMediaWidths.css[width]);
129	};
130
131	$.fn.VerticalMenu = function (args) { return args ? new VerticalSlideMenu(args) : this.eq(0).data("VerticalMenu") };
132
133	(VerticalSlideMenu = function (o) { this.init(o) }).prototype = {
134		$drop: $(),
135		$overlay: $(),
136		minSwipe: 2,
137		scrollMinDur: 0.4,
138		scrollDistDiv: 5000,
139		titleWidth: "66%",
140		fullClass: "sm-full-",
141		openClass: "sm-open-",
142		closeClass: "sm-close",
143		effect: 1, // [1 - 14]
144		effectEx: { '-1': 1, '1': 1, '6': 1, '9': 1, '11': 1, '12': 1 },
145		popupDur: 0.6,
146		baseCSS: {
147			x: 0, y: 0,
148			opacity: 1,
149			rotationX: 0,
150			rotationY: 0,
151			rotationZ: 0,
152			skewX: 0, scaleX: 1,
153			skewY: 0, scaleY: 1
154		},
155
156		init(obj) {
157			$.extend(this, obj);
158			$body = $body || $(document.body);
159			this.$node = $('#off-menu_' + this.id).data("VerticalMenu", this);
160			this.$parent = this.$node.parent().addClass("sm-parent");
161			this.$head = this.$node.find(".sm-head");
162			this.$back = this.$head.find(".sm-back");
163			this.$title = this.$head.children(":last");
164			this.$lvls = this.$node.find(".sm-levels");
165			this.$filter = this.$node.find(".sm-filter").attr("value", "");
166			this.$reset = this.$node.find(".sm-reset").attr("tabindex", -1);
167			this.$dt = this.$lvls.find("dt.parent, dt.notparent");
168			this.fullClass += this.id;
169			this.openClass += this.id;
170			this.menuPadding = parseInt(this.$lvls.children(':first').css('paddingLeft'));
171			this.bindUpdateScrollbar = smMobile ? undefined : this.updateScrollbar.bind(this, 0);
172			this.$lvls.find('a').map(function (i, a) { // scrollspy
173				var hash = a.href.match(/#([\w-]+)$/);
174				return hash ? document.getElementById(hash[1]) : undefined;
175			}).menuspy().length && $body.on('scrollEnter.menuspy', function (e) {
176				this.$lvls.find('a[href$="#' + e.target.id + '"]').closest('dt').addClass('active').siblings('.active').removeClass('active');
177			}.bind(this)).on('scrollLeave.menuspy', function (e) {
178				this.$lvls.find('a[href$="#' + e.target.id + '"]').closest('dt').removeClass('active');
179			}.bind(this));
180
181			this.$node.find('.sm-x').attr({ tabindex: 0, role: 'button' }).on('click.sm touchend.sm keydown.sm', function (e) {
182				if (e.type == 'keydown' && e.keyCode != 13 && e.keyCode != 32) return;
183				this.closeMenu();
184			}.bind(this));
185
186			this.dropmenu = this.navtype == 'drop';
187			this.accordion = this.navtype == 'accordion';
188			this.treemenu = this.navtype == 'tree' || this.accordion;
189			this.expand = this.navtype == 'expand' || this.treemenu;
190
191			if (this.overlay && !this.expand) { // override menu type
192				this.dropmenu = 0;
193				this.expand = 1;
194				this.treemenu = this.navtype != 'expand';
195			}
196			if ((this.treemenu || this.expand) && !this.$filter.length) this.backItem = '';
197			if (this.popup || this.overlay) this.sidebarUnder = 0;
198			if (this.treemenu)
199				this.$node.addClass("sm-tree").on("click.sm touchend.sm", "dt.parent", $.proxy(this, "onClickTree"));
200			this.build();
201			this.onResize();
202			if (!this.sidebar && this.winWidth >= this.sidebarUnder) this.$lvls.height(''); // height fix for webkit
203			if (this.filterMinChar < 1) this.filterMinChar = 1;
204
205			this.bg = this[this.overlay ? "$overlay" : "$node"].css("backgroundImage").match(/url\(['"]?(.+?)['"]?\)/i);
206
207			if (this.popup || this.sidebar || this.overlay || this.winWidth < this.sidebarUnder) {
208				// only make the autoopen work if the menu is not in module position
209				var w = $(window).width(), visible = true;
210				if (!+this.visibility[4]) { // basic
211					if (w < 768 && !+this.visibility[0]) visible = false;
212					if (w >= 768 && w < 992 && !+this.visibility[1]) visible = false;
213					if (w >= 992 && w < 1200 && !+this.visibility[2]) visible = false;
214					if (w >= 1200 && !+this.visibility[3]) visible = false;
215				} else { // advanced
216					if (this.visibility[5][0] > w || this.visibility[6][0] < w) visible = false;
217				}
218				if (visible) switch (this.autoOpen) {
219					case 1: // Only once
220						if (!window.sessionStorage || sessionStorage.smAutoOpen) break;
221						sessionStorage.smAutoOpen = 1;
222					case 2: // Always
223						if (w < 768 && this.autoOpen == 2) break; // disable always auto open on mobile
224						if (this.autoOpenAnim) {
225							// animate auto open
226							$win.one('load.sm', $.proxy(this, "openMenu"));
227						} else {
228							// don't animate auto open
229							if (this.popup) {
230								this.$button.eq(1).hide();
231								this.$noTrans = function () { this.$button.eq(1).show(), delete this.$noTrans }.bind(this);
232							} else {
233								this.$noTrans = this.$node
234									.add(this.$pusher)
235									.add(this.$content)
236									.add(this.$overlay.parent())
237									.add(this.$button.hide())
238									.css('transitionDuration', '1ms')
239								;
240							}
241							$(this.openMenu.bind(this));
242						}
243				}
244			}
245
246			// add events
247			if (this.parentHref) // don't slide to submenu
248				this.$node.on("click.sm touchend.sm", "dt.parent a", $.proxy(this, "onRedirect"));
249			else // disable redirection when pointer-events:none isn't supported
250				this.$node.on("click.sm", "dl:not(.sm-result) dt.parent a", e => e.preventDefault());
251			this.$node
252				.on("click.sm touchend.sm", this.expand && !this.treemenu ? "dt" : ".notparent", $.proxy(this, "onRedirect"))
253				.on("click.sm touchend.sm keydown.sm", ".sm-back, .sm-back-item", function (e) {
254					if (e.type == 'keydown' && e.keyCode != 13 && e.keyCode != 32) return;
255					$.proxy(this, "onBack")(e);
256				}.bind(this))
257				.on("click.sm touchend.sm keydown.sm", ".sm-reset", function (e) {
258					if (e.type == 'keydown' && e.keyCode != 13 && e.keyCode != 32) return;
259					$.proxy(this, "onResetFilter")(e);
260				}.bind(this))
261				.on("click.sm", e => e.stopPropagation());
262			if (!this.expand) this.$node.on("click.sm touchend.sm", "dt.parent", $.proxy(this, "onOpen"));
263			this.$filter
264				.on("keyup.sm", $.proxy(this, "onFilter"));
265			this.$button.attr({ tabindex: 10, role: 'button' }).on("click.sm keydown.sm", function (e) {
266				if (e.type == 'keydown' && e.keyCode != 13 && e.keyCode != 32) return;
267				$.proxy(this, "onClickButton")(e);
268			}.bind(this)); // touch event causes problems on scroll
269			this.$lvls
270				.on("touchstart.sm", $.proxy(this, "onTouchStart"))
271				.on("touchmove.sm", $.proxy(this, "onTouchMove"))
272				.on("touchend.sm touchcancel.sm", $.proxy(this, "onTouchEnd"))
273				.on("mousedown.sm", $.proxy(this, "onMouseDown"));
274			$win
275				.on("resize.sm", $.proxy(this, "onResize"))
276				.on("keyup.sm", $.proxy(this, "onEsc"));
277			if (this.bg) $win.on("load.sm", Hlpr.loadImage.bind(0, this.bg[1]));
278			$body.one("touchend.sm", $.proxy(this, "touchOptimize"));
279		},
280
281		touchOptimize() {
282			this.$node
283				.off("click.sm", "dt.parent")
284				.off("click.sm", "dt")
285				.off("click.sm", ".notparent")
286				.off("click.sm", ".sm-back, .sm-back-item");
287			if (this.dropmenu)
288				this.$drop.off("click.sm", "dt");
289		},
290
291		build() {
292			// build menu icon
293			this.$button = $('<div class="menu-icon-cont sm-btn-' + this.id + '">')
294				.html('<div class="menu-icon3"><span></span><span></span><span></span></div>');
295
296			if (this.dropmenu) {
297				this.anim.inRotationY = this.anim.inCSS.rotationY;
298				this.anim.outRotationY = this.anim.outCSS.rotationY;
299				this.miRotationY = this.miCSS.rotationY;
300				this.$drop = $('<nav>').css({
301					position: 'absolute',
302					left: 0, right: 0,
303					top: 0, zIndex: 999
304				}).addClass("sm-drop off-menu_" + this.id)
305					.appendTo($body)
306					.on("click.sm touchend.sm", "dt", $.proxy(this, "onRedirect"))
307			}
308
309			// plus svg animation hack
310			$(this.$lvls[0].querySelectorAll('.sm-arrow use[*|href*=plus]')).each(function () {
311				$(this).clone().insertAfter(this);
312			});
313
314			//add logo link
315			var link = this.logoUrl;
316			if (link) $("nav.off-menu_" + this.id + " .sm-logo img").css("cursor", "pointer").click(function (e) {
317				document.location.href = link;
318			});
319
320			if (this.popup) { // POPUP
321				this.initPopup();
322			} else if (this.sidebar || this.sidebarUnder) { // SIDEBAR
323				this.initContainer();
324				this.initSidebar();
325			} else if (this.overlay) { // OVERLAY
326				this.initContainer();
327				this.initOverlay();
328			}
329
330			// build submenus
331			var $div = this.$lvls.find(".sm-level");
332			$div.push( // add level for search results
333				$('<div class="sm-level level2"><dl class="sm-result level2">')
334					.css('display', 'none').appendTo(this.$lvls)[0]);
335			this.opened = $div[0];
336			this.mainmenu = this.opened;
337			if (this.treemenu) this.$lvls.find('dd.opened').css('display', 'block'); // init tree/accordion animation
338			if (this.backItem) {
339				var $backItem = $("dt:first", $div[0]).clone().removeClass("parent notparent active").addClass("sm-back-item");
340				$backItem.find("a").removeAttr("href").html(this.backItem);
341				$backItem.find(".desc, .sm-icon, .productnum").remove();
342			}
343			if (!this.expand) {
344				var level, div, i;
345				for (i = 1; i < $div.length; i++) {
346					if (this.backItem && (!this.dropmenu || i == $div.length - 1)) // if drop, add only for search results
347						$backItem.clone().prependTo($div[i].children[0]); // add back menu-item to sublevels
348					div = $div[i];
349					div.style.display = "none";
350					div.parent = $(div.parentNode).closest(".sm-level")[0];
351					div.parentItem = $(div.parentNode).prev()[0];
352					if (div.parentNode.tagName == "DD")
353						$(div.parentNode).prev()[0].menu = div;
354				}
355			}
356			this.$result = this.$lvls.children(":last");
357			if (this.expand && this.backItem) $backItem.clone().prependTo(this.$result[0].children[0]); // add back to search result
358		},
359
360		initPopup() {
361			var $clone = this.$button.clone().css({
362				transform: "translateX(-100%)",
363				zIndex: -1
364			}).appendTo(this.$node);
365			this.$node.css({
366				display: "block", position: "absolute",
367				visibility: "hidden", opacity: 0,
368				left: 0, top: 0,
369				width: this.width,
370				margin: 0, zIndex: 99999,
371				WebkitTransition: "none", MozTransition: "none", msTransition: "none", transition: "none"
372			}).addClass("sm-popup");
373			if (this.menuIconCorner) { // button in corner
374				this.$node.appendTo(this.$button);
375				this.$button.appendTo($body);
376			} else { // button in module position
377				this.$node.appendTo($body);
378				this.$button.appendTo(this.$parent);
379			}
380			this.$button.addClass('sm-popup-burger').push($clone[0]);
381			this.$cont = $body;
382			this.effect = 0;
383		},
384
385		initContainer() {
386			this.htmlCSS = {
387				background: $html.css("backgroundColor") + " " + $html.css("backgroundImage") + " " + $html.css("backgroundRepeat") + " " +
388					$html.css("backgroundAttachment") + " " + $html.css("backgroundPosition") + " / " + $html.css("backgroundSize")
389			};
390			this.bodyCSS = {
391				overflowY: $body.css("overflowY"),
392				borderTop: $body.css("borderTopWidth") + " " + $body.css("borderTopStyle") + " " + $body.css("borderTopColor"),
393				borderBottom: $body.css("borderBottomWidth") + " " + $body.css("borderBottomStyle") + " " + $body.css("borderBottomColor"),
394				borderLeft: $body.css("borderLeftWidth") + " " + $body.css("borderLeftStyle") + " " + $body.css("borderLeftColor"),
395				borderRight: $body.css("borderRightWidth") + " " + $body.css("borderRightStyle") + " " + $body.css("borderRightColor"),
396				padding: $body.css("paddingTop") + " " + $body.css("paddingRight") + " " + $body.css("paddingBottom") + " " + $body.css("paddingLeft"),
397				background: $body.css("backgroundColor") + " " + $body.css("backgroundImage") + " " + $body.css("backgroundRepeat") + " " +
398					$body.css("backgroundAttachment") + " " + $body.css("backgroundPosition") + " / " + $body.css("backgroundSize")
399			};
400
401			this.$cont = $body.addClass("sm-container");
402			this.$pusher = $(".sm-pusher");
403			if (!this.$pusher.length) {
404				var $cnt = $body.children(':not(nav.sm-menu)');
405				this.$pusher = $('<div class="sm-pusher"><div class="sm-content"><div class="sm-content-inner">').appendTo($body);
406			}
407			this.$content = this.$pusher.children();
408			this.$inner = this.$content.children(".sm-content-inner").append($cnt);
409
410			$html.css('overflow-y', 'auto');
411		},
412
413		initSidebar() {
414			if (this.sidebar) {
415				this.$button.css("display", "");
416				this.$node.appendTo(this.$cont);
417			}
418			this.$cont.on("click.sm touchstart.sm", function (e) { this.effect < 0 || this.closeMenu(e) }.bind(this));
419			// disable page scrolling when sidebar is open
420			this.$lvls.on("touchstart.sm", function (e) { this.effect < 0 || this.scrollFixStart(e) }.bind(this));
421			this.$cont.on("touchmove.sm", function (e) { this.effect < 0 || this.scrollFixMove(e) }.bind(this));
422
423			// TransitionEnd fix
424			this.$node.on('transitionend', Hlpr.cancelTransitionEnd);
425			this.$inner.on('transitionend', Hlpr.cancelTransitionEnd);
426
427			if (this.menuIconCorner) {
428				this.$button.appendTo(this.$inner);
429			} else {
430				this.$button.appendTo(this.$parent);
431				this.$button.push(this.$button.clone().css("opacity", 0).appendTo(this.$node)[0]);
432			}
433			if (!this.sidebar) this.$button.css("display", "none");
434		},
435
436		initOverlay() {
437			var $overlay = $('<div>').addClass("sm-overlay-" + this.id).appendTo($body);
438			this.$overlay = $('<div>').addClass("sm-overlay-win").appendTo($overlay)
439				.on("click.sm transitionend", e => e.stopPropagation()) // don't close overlay on click & TransitionEnd fix
440			this.$node.appendTo(this.$overlay);
441			this.$cont.on("click.sm touchstart.sm", $.proxy(this, "closeMenu"));
442			// disable touch-scrolling when sidebar is open
443			this.$lvls.on("touchstart.sm", $.proxy(this, "scrollFixStart"));
444			this.$cont.on("touchmove.sm", $.proxy(this, "scrollFixMove"));
445
446			if (this.menuIconCorner) {
447				this.$button.appendTo($body);
448			} else {
449				this.$button.appendTo(this.$parent);
450				this.$button.push(this.$button.clone().css("opacity", 0).insertBefore(this.$node)[0]);
451			}
452		},
453
454		scrollFixStart(e) {
455			this.touchstartY = e.originalEvent.touches[0].clientY;
456		},
457
458		scrollFixMove(e) {
459			if (!this.$cont.hasClass(this.openClass)) return;
460			else if (!$(e.target).closest('.sm-levels').length) return e.preventDefault();
461
462			var node = this.opened.children[0];
463			if (this.touchstartY < e.originalEvent.touches[0].clientY) {  // on scroll up
464				if (node.scrollTop == 0) e.preventDefault();
465			} else { // on scroll down
466				if (node.scrollTop == node.scrollHeight - node.offsetHeight) e.preventDefault();
467			}
468		},
469
470		onMouseDown(e) {
471			if (e.button == 0) {
472				if (e.target.tagName == "A") e.preventDefault();
473				this.onTouchStart(e);
474				$body
475					.on("mousemove.sm", $.proxy(this, "onTouchMove"))
476					.on("mouseup.sm", $.proxy(this, "onMouseUp"));
477			}
478		},
479
480		onMouseUp(e) {
481			if (e.button == 0) {
482				$body.off("mousemove.sm mouseup.sm");
483				this.onTouchEnd(e);
484			}
485		},
486
487		onTouchStart(e) {
488			var touch = e.originalEvent.touches && e.originalEvent.touches[0] || e;
489			this.touchStartX = this.touchPrevX = touch.pageX;
490			this.touchStartY = this.touchPrevY = touch.pageY;
491			this.touchTarget = e.target;
492			delete this.touchEvent;
493		},
494
495		onTouchMove(e) {
496			if (this.touchEvent == "scroll") return;
497			if (smMobile && smMobile[0] == "Windows Phone") e.preventDefault(); // disable scrolling or swipe back
498			var touch = e.originalEvent.touches && e.originalEvent.touches[0] || e,
499				x = touch.pageX,
500				y = touch.pageY;
501			diffX = x - this.touchStartX,
502				diffY = y - this.touchStartY;
503			if (this.touchEvent === undefined) {
504				var dx = Math.abs(diffX),
505					dy = Math.abs(diffY);
506				if (dy > this.minSwipe && dy >= dx) { // vertical swipe
507					this.touchEvent = "scroll";
508					$body.addClass("sm-scroll");
509				} else if (!this.opened.parent) {
510					// don't check back swipe on main level
511				} else if (diffX > this.minSwipe && dx > dy) { // back swipe
512					if (this.openTL && this.openTL.isActive()) {
513						if (this.openTL.time() < this.dur) return;
514						else this.openTL.pause();
515					}
516					this.touchEvent = "back";
517					if (!smMobile) $(this.opened.children).perfectScrollbar("destroy");
518					this.opened.parent.style.display = "block";
519					this.initBackTimeline();
520					gsap.set(this.$lvls[0], { perspective: this.perspective }); // blur fix for retina
521					this.nodeW = this.$lvls.outerWidth();
522				}
523			}
524			if (this.touchEvent == "back") {
525				e.preventDefault(); // disable scrolling
526				diffX -= this.minSwipe;
527
528				if (diffX <= 1) diffX = 0.1;
529				if (diffX >= this.nodeW) diffX = this.nodeW - 0.1;
530				this.backTL.seek(this.dur * diffX / this.nodeW);
531
532				this.touchDirLeft = this.touchPrevX < x;
533				this.touchDirTop = this.touchPrevY < y;
534				if (this.touchPrevX != x) this.touchPrevX = x;
535				if (this.touchPrevY != y) this.touchPrevY = y;
536			}
537		},
538
539		onTouchEnd(e) {
540			var touch = e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] || e,
541				dx = Math.abs(this.touchStartX - touch.pageX),
542				dy = Math.abs(this.touchStartY - touch.pageY);
543
544			if (!this.touchEvent && dx <= this.minSwipe && dy <= this.minSwipe) return;
545			e.preventDefault();
546			e.stopPropagation();
547
548			if (this.touchEvent == "scroll") {
549				$body.removeClass("sm-scroll");
550			} else if (this.touchEvent == "back") {
551				// disable click when swipe finished on the same menu-item where it started
552				if (this.touchTarget == e.target) this.disableClick = true;
553				if (this.touchDirLeft) {
554					this.back();
555				} else {
556					if (!smMobile) $(this.opened.children).perfectScrollbar();
557					this.backTL.reverse();
558				}
559			}
560		},
561
562		onEsc(e) {
563			if (e.keyCode == 27 && !this.$button.eq(1).hasClass("sm-hide") && this.$button.css('display') != 'none') {
564				e.stopPropagation();
565				this.closeMenu();
566			}
567		},
568
569		onClickButton(e) {
570			e.stopPropagation();
571			e.preventDefault();
572			if (this.$button.hasClass("sm-hide")) {
573				gsap.set(this.$button[0], { x: this.menuIconX });
574				this.$button.css('transform', '').removeClass("sm-hide");
575				return;
576			}
577			if (this.popup) { // POPUP
578				if (this.$node.css("visibility") == "hidden") {
579					this.openMenu();
580				} else {
581					if (e.currentTarget == this.$button[0]) return; // don't close menu for any click
582					this.closeMenu();
583				}
584			} else { // SIDEBAR || OVERLAY
585				$(e.currentTarget).hasClass(this.closeClass) ? this.closeMenu() : this.openMenu();
586			}
587		},
588
589		onOpenPopup() {
590			var bw = this.$button.width(),
591				bh = this.$button.height(),
592				w = this.$node.width(),
593				h = this.$node.height();
594			this.$node.css("visibility", "visible");
595			if (this.menuIconCorner) {  // fixed position
596				gsap.set(this.$node[0], { transformOrigin: "0% 0% 0" });
597			} else {  // module position
598				this.onResize();
599			}
600			var tl = gsap.timeline({ paused: true, autoRemoveChildren: true, onComplete: this.$noTrans });
601			tl.add(gsap.fromTo(this.$node[0], { scaleX: bw / w, scaleY: bh / h }, {
602				duration: 1,
603				css: { scaleX: 1, scaleY: 1 },
604				ease: "back.out"
605			}), 0);
606			tl.add(gsap.to(this.$node[0], {
607				duration: 1,
608				opacity: 1,
609				onComplete: $.proxy(this.$button.eq(1), "addClass", this.closeClass)
610			}), 0);
611			tl.add(gsap.fromTo(this.$button[1], { x: -bw, opacity: 1 }, {
612				duration: 0.75,
613				x: 0
614			}), -0.5);
615			tl.duration(this.$noTrans ? 0.001 : this.popupDur);
616			tl.play();
617			this.$cont.addClass(this.openClass);
618			this.$node.trigger("openMenu", this);
619		},
620
621		onClosePopup() {
622			var bw = this.$button.width(),
623				bh = this.$button.height(),
624				w = this.$node.width(),
625				h = this.$node.height();
626			this.$button.removeClass(this.closeClass);
627			var tl = gsap.timeline({ paused: true, autoRemoveChildren: true });
628			tl.add(gsap.to(this.$button[1], { duration: 0.3, x: -bw }), -0.15);
629			tl.add(gsap.to(this.$node[0], {
630				duration: 0.3,
631				opacity: 0,
632				ease: "quad.in"
633			}), 0.3);
634			tl.add(gsap.to(this.$node[0], {
635				duration: 0.3,
636				scaleX: bw / w,
637				scaleY: bh / h,
638				onComplete: $.proxy(this.$node, "css", "visibility", "hidden")
639			}), 0.3);
640			tl.play();
641			this.$cont.removeClass(this.openClass);
642			this.$node.trigger("closeAllDrops", this);
643			this.$node.trigger("closeMenu", this);
644			this.$node.trigger("closeMenu", this);
645		},
646
647		openMenu(e, effect) {
648			if (this.$cont.hasClass(this.openClass)) return;
649			var otherOpen = this.$cont[0].className.match(/\bsm-open-(\d+)\b/);
650			if (otherOpen) { // if another menu is already open
651				window['sm' + otherOpen[1]].closeMenu();
652				return setTimeout($.proxy(this, 'openMenu'), 550);
653			}
654			if (e && e.stopPropagation) e.stopPropagation();
655			if (this.popup) return this.onOpenPopup();
656
657			$body.off("transitionend.tr").one("transitionend.tr", ".sm-pusher", $.proxy(this, "onEndOpenMenu"));
658
659			this.effect = effect || this.effect;
660			if (this.effect < 0) {
661				$html.addClass('sm-reduce-width');
662				hackMediaWidths(this.width);
663				$.fn.width = Hlpr.hackWidth(this.width);
664			}
665			this.$cont.addClass("sm-effect-" + Math.abs(this.effect));
666
667			var btn = this.$button.length && (!this.menuIconCorner || this.overlay || this.effectEx[this.effect]);
668			if (btn && this.menuIconCorner) this.$button.appendTo(this.overlay ? this.$overlay : this.$node);
669
670			this.scroll = $win.scrollTop();
671			if (smMobile) {
672				this.effect < 0 ? $win.off("resize.sm") : $win.off("resize.sm").scrollTop(0); // don't resize during animation on mobile
673			}
674
675			this.$cont.css({ position: 'fixed', top: 0, bottom: 0 });
676			$html.addClass(this.fullClass);
677			this.$inner.css(this.bodyCSS);
678			this.$content.css(this.htmlCSS);
679
680			if (!smMobile) {
681				if (btn && !this.menuIconCorner) {
682					if (!this.overlay) this.$button.eq(1).appendTo(this.effectEx[this.effect] ? this.$node : this.$inner);
683					this.$button[1].style.display = "inline-block";
684				}
685			} else { // mobile
686				if (this.$button[1]) this.$button[1].style.display = "block";
687			}
688
689			if (this.overlay) {
690				this.$content.css('maxWidth', '100vw'); // Firefox bugfix (#18)
691				this.$lvls.css('maxHeight', '');
692			} else if (this.sidebar || this.winWidth < this.sidebarUnder) {
693				if (this.effect > 0) this.$content.css('maxWidth', '100vw'); // Firefox bugfix (#18)
694				this.$lvls.css('maxHeight', this.effect == 8 ? window.innerHeight : window.innerHeight - this.$lvls.position().top);
694 // fix for sidebar effect 8
695			}
696
697			this.$content.scrollTop(this.scroll);
698			this.$cont.css("background", this.siteBg).addClass(this.openClass);
699			if (this.$pusher && this.$pusher.length) this.$pusher.attr('inert', '');
700			this.$node.trigger("openMenu", this);
701		},
702
703		onEndOpenMenu(e) {
704			if (smMobile) {
705				$win.on("resize.sm", $.proxy(this, "onResize")); // allow resize after animation on mobile
706				if (this.sidebar || this.winWidth < this.sidebarUnder) // fix for sidebar bottom can't be see
707					this.$lvls.css('maxHeight', window.innerHeight - this.$lvls.position().top);
708			}
709			if (this.effect < 0 && this.winWidth > 767) $win.resize();
710			if (this.menuIconCorner)
711				setTimeout($.proxy(this.$button, "addClass", this.closeClass), 1);
712			else {
713				this.$button[0].style.opacity = 0;
714				this.$button[1].style.opacity = 1;
715				this.$button.eq(1).addClass(this.closeClass);
716			}
717			if (this.overlay) { // calculate overlay max-height after transition
718				var topHeight = 0;
719				this.$lvls.prevAll().each(function () { topHeight += $(this).outerHeight() });
720				this.$lvls.css("maxHeight", this.$overlay.outerHeight() - topHeight);
721			} else if (this.effect == 8) { // fix for sidebar effect 8
722				this.$lvls.css('maxHeight', this.$cont.outerHeight() - this.$lvls.position().top);
723			}
724			this.$node.css("zIndex", 99);
725			if (this.$noTrans) {
726				this.$button.addClass(this.closeClass).show();
727				this.$noTrans.css('transitionDuration', '');
728				delete this.$noTrans;
729			}
730			this.updateScrollbar();
731		},
732
733		closeMenu(e) {
734			if (e && $(e.target).closest(".sm-menu").length) return;
735			if (!this.$cont || !this.$cont.hasClass(this.openClass)) return;
736			if (this.popup) return this.onClosePopup();
737			$body.off("transitionend.tr").one("transitionend.tr", this.overlay ? ".sm-overlay-" + this.id : "", $.proxy(this, "onEndCloseMenu"));
738			if (!this.menuIconCorner) {
739				this.$button[1].style.display = "none";
740				this.$button[1].style.opacity = 0;
741			}
742			if (smMobile) this.$filter.blur();
743			this.$cont.removeClass(this.openClass);
744			this.$node.css("zIndex", "").trigger("closeMenu", this);
745		},
746
747		onEndCloseMenu(e) {
748			if (this.effect < 0) {
749				$.fn.width = Hlpr.width;
750				hackMediaWidths(0);
751				this.scroll = $win.scrollTop();
752				$html.removeClass('sm-reduce-width');
753				if (this.winWidth >= 768) $win.resize();
754			}
755			var s = this.$inner[0].style;
756			s.position = s.background = s.border = s.padding = s.margin = "";
757			$body[0].style.background = $body[0].style.position = $body[0].style.top = $body[0].style.bottom = "";
758			this.$content.css({ height: '', background: '' });
759			$html.removeClass(this.fullClass);
760			//$html.css("overflowY", "");
761			if (smMobile) this.$node.css({ top: "", position: "" });
762			$html.css('scrollBehavior', 'auto');
763			$win.scrollTop(this.scroll);
764			$html.css('scrollBehavior', '');
765			this.$cont.removeClass("sm-effect-" + Math.abs(this.effect));
766			if (this.$pusher && this.$pusher.length) this.$pusher.removeAttr('inert');
767
768			this.$content.css('maxWidth', ''); // Firefox bugfix (#18)
769			if (this.$button.length) {
770				if (!this.menuIconCorner || this.overlay || this.effectEx[this.effect]) {
771					if (this.menuIconCorner) this.$button.appendTo(this.$inner);
772					else this.$button[0].style.opacity = 1;
773					setTimeout($.proxy(this.$button, "removeClass", this.closeClass), 1);
774				}
775				else this.$button.removeClass(this.closeClass);
776			}
777		},
778
779		onResize(e) {
780			var isInCont = this.$node[0].parentNode != this.$parent[0],
781				$title = this.$title.last();
782			this.prevWinWidth = this.winWidth || $win.outerWidth();
783			this.winWidth = $win.outerWidth();
784			this.winHeight = $win.outerHeight();
785			// update button
786			if (!this.sidebar && this.winWidth < this.sidebarUnder && !isInCont) {
787				this.$button.css("display", "");
788				this.$node
789					.appendTo(this.$cont)
790					.trigger("moveToSidebar", this);
791			}
792
793			// update title position (don't update on init)
794			if (e && $title[0]) gsap.set($title[0], { x: $title._center(this.$back.length) });
795
796			// update dropmenu
797			var drop = this.drop;
798			this.drop = this.dropmenu && !smMobile && this.winWidth >= this.sidebarUnder;
799			if (drop != this.drop) {
800				if (this.drop) {
801					if (this.$head.length) {
802						this.$title.filter(":not(:first)").remove();
803						this.$title.length = 1;
804						gsap.set(this.$title[0], { scale: 1, x: this.$title.attr("class", "sm-title")._center(this.$back.length) });
805					}
806					this.$lvls.children().attr("style", "display: none;");
807					this.opened = this.$lvls.children(":first").attr("style", "position: static;")[0];
808					this.prev = undefined;
809					this.$node
810						.on(this.dropEvent + ".dm", "dt.parent", $.proxy(this, "onOpenDrop"))
811						.on(this.dropEvent + ".dm", ">.sm-level", $.proxy(this, "onMouseEnterDrop"));
812					this.$drop
813						.on(this.dropEvent + ".dm", "dt.parent", $.proxy(this, "onOpenDrop"))
814						.on(this.dropEvent + ".dm", ".sm-level", $.proxy(this, "onMouseEnterDrop"));
815					if (this.dropEvent == "click") {
816						$body.on("click.dm", $.proxy(this, "closeAllDrops"));
817					} else {
818						this.$node
819							.on("mouseleave.dm", "dt.parent", $.proxy(this, "onCloseDrop"))
820							.on("mouseleave.dm", ">.sm-level", $.proxy(this, "onMouseLeaveDrop"));
821						this.$drop
822							.on("mouseleave.dm", "dt.parent", $.proxy(this, "onCloseDrop"))
823							.on("mouseleave.dm", ".sm-level", $.proxy(this, "onMouseLeaveDrop"));
824					}
825				} else {
826					this.$node.off(".dm");
827					this.$drop.off(".dm");
828					$body.off("click.dm");
829				}
830			}
831			// update background img pos
832			if (this.bg && this.bgX) {
833				var level = this.opened.children[0].attributes['class'].value.match(/\blevel(\d)\b/)[1];
834				this.$node[0].backgroundPosition = this.bgX * (level - 1) + "% 0";
835			}
836
837			if (this.popup) {
838				var maxH, mb = navigator.userAgent.match(/iPad/i) ? 25 : 5;
839				if (this.menuIconCorner) {  // fixed position
840					var off = this.$lvls.offset();
841					$body.on("click.dm", $.proxy(this, "closeAllDrops"));
842
843					maxH = (this.winHeight - (off.top - $win.scrollTop()) - mb);
844					this.opened.children[0].style.maxHeight = maxH - 2 * this.menuPadding + "px";
845					this.$lvls.css("maxHeight", maxH);
846					this.$node.css("maxWidth", this.winWidth - off.left - this.$button.width());
847				} else if (this.$node.css("visibility") != "hidden") { // module position
848					var btn = this.$button.offset(),
849						top = $win.scrollTop(),
850						originX = "0%";
851					if (this.winWidth / 2 > btn.left || this.popup == 2) { // left
852						this.$node.css({
853							maxWidth: this.winWidth - btn.left - this.$button.width(),
854							left: btn.left,
855							right: ""
856						});
857					} else {  // right
858						originX = "100%";
859						this.$node.css({
860							maxWidth: btn.left,
861							left: "",
862							right: this.winWidth - btn.left - this.$button.width()
863						});
864					}
865					if (top + this.winHeight / 2 > btn.top) { // top
866						gsap.set(this.$node[0], { transformOrigin: originX + " 0% 0", scaleX: 1, scaleY: 1 });
867						maxH = this.winHeight - (btn.top - top) - this.$lvls.position().top - mb;
868						this.$node.css({ top: btn.top, bottom: "" });
869					} else { // bottom
870						gsap.set(this.$node[0], { transformOrigin: originX + " 100% 0", scaleX: 1, scaleY: 1 });
871						maxH = btn.top - top + this.$button.height() - this.$lvls.position().top;
872						this.$node.css({ top: "", bottom: this.winHeight - btn.top - this.$button.height() });
873					}
874					this.opened.children[0].style.maxHeight = maxH - 2 * this.menuPadding + "px";
875					this.$lvls.css({ maxHeight: maxH, height: $(this.opened.children).height() + 2 * this.menuPadding });
876				}
877			} else if (this.sidebar || this.sidebarUnder) {
878				// update button & sidebar
879				if (!this.sidebar && this.winWidth >= this.sidebarUnder && isInCont) {
880					this.$button.css("display", "none");
881					this.$node
882						.css({ top: "", position: "" }) // mobile fix
883						.appendTo(this.$parent);
884					if (this.$cont.hasClass(this.openClass)) {
885						this.closeMenu();
886						this.onEndCloseMenu();
887					}
888					this.$node.trigger("moveToModulepos", this);
889				}
890				if (this.$node.css("display") == "block") { // if sidebar is open
891					this.$pusher.addClass("no-trans"); // disable transitions during resize
892					if (this.$pusher[0]) this.$pusher[0].offsetHeight; // trigger a reflow, flushing the CSS changes
893					this.$pusher.removeClass("no-trans");
894					// update menu height
895					if (this.sidebar || this.winWidth < this.sidebarUnder)
896						this.$lvls.css('maxHeight', window.innerHeight - this.$lvls.position().top);
897					else if (!this.treemenu)
898						this.$lvls.height($(this.opened.children).outerHeight() + 2 * this.menuPadding);
899					else
900						this.$lvls[0].style.height = "";
901				} else { // if sidebar is close
902					if (this.hideBurger && this.autoOpen == 2 && this.prevWinWidth < 768 && this.winWidth >= 768) this.openMenu();
903				}
904			} else if (this.overlay && $body.hasClass(this.openClass)) { // OVERLAY
905				var topHeight = 0;
906				this.$lvls.prevAll().each(function () { topHeight += $(this).outerHeight() });
907				this.$lvls.css('maxHeight', this.$overlay.outerHeight() - topHeight);
908			}
909
910			this.updateScrollbar();
911		},
912
913		onFilter(e) {
914			var val = this.$filter.val();
915			this.$filter.attr('value', val);
916			this.$reset.attr('tabindex', val.length ? 0 : -1);
917			if (!val.length && $(this.opened.children).hasClass("sm-result")) this.onBack();
918			if (this.filterTimeout) this.filterTimeout = clearTimeout(this.filterTimeout);
919			if (val.length < this.filterMinChar) return;
920			this.filterTimeout = setTimeout($.proxy(this, "filter", val), this.filterDelay);
921		},
922
923		filter(keyword) {
924			keyword = keyword.trim().replace(/\s+/g, " ").replace(/([.?*+^$|{}
924()\\[\]])/g, "\\$1");
925			keyword = keyword.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
926			if (this.keyword == keyword) return;
927			if (!this.keyword) this.filterParent = this.opened;
928			this.keyword = keyword;
929			this.$filter.trigger("filter", [this, keyword]);
930			var i, $dt,
931				$result = this.$result.clone().appendTo(this.$lvls).children(),
932				submenu = $result[0].parentNode;
933			for (i = 0; i < this.$dt.length; i++) {
934				$dt = $(this.$dt[i]);
935				$filter_text = $dt.text().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
936				if ($filter_text.match(new RegExp(this.keyword, "i"))) {
937					var $clone = $dt.clone().removeClass("parent").addClass("notparent");
938					$clone[0].innerHTML = $clone[0].innerHTML.replace(new RegExp(">([^<]*)(" + this.keyword + ")([^>]*)<", "ig"), ">$1<strong>$2</strong>$3<");
939					$clone.appendTo($result);
940				}
941			}
942			var res = $result[0].children.length;
943			if (this.backItem && res == 1 || !res) {
944				var $no = $(this.opened).find("dt:first").clone()
945					.removeClass("parent opened").addClass("notparent").css("pointerEvents", "none");
946				$no.find("a").removeAttr("href").html(this.noResult);
947				$no.find(".desc, .sm-icon, .productnum").remove();
948				$no.appendTo($result);
949			}
950			submenu.parent = this.filterParent;
951			this.open(this.result, submenu);
952		},
953
954		onRedirect(e) {
955			e.stopPropagation();
956			if (this.disableClick) return e.preventDefault(); // disable click during swipe
957			var $a = e.currentTarget.tagName == "A" ? $(e.currentTarget) : $("a", e.currentTarget),
958				a = $a[0], event = $.Event("openMenuItem");
959			$a.trigger(event, this);
960			if (event.isDefaultPrevented()) return e.preventDefault();
961			var $scrollTo = $(), hash = a.href && a.href.split('#')[1];
962			if (hash && location.href.split('#')[0] == a.href.split('#')[0])
963				$scrollTo = hash == 'top' ? $body : $('#' + hash + ', a[name="' + hash + '"]');
964			if ($scrollTo.length) { // scroll
965				e.preventDefault();
966				var $cont, cont, t, b;
967				if (this.$cont && this.$cont.hasClass(this.openClass) && this.effect > 0) { // sidebar
968					$cont = this.$content;
969					cont = { scroll: $cont.scrollTop() };
970					// t = $scrollTo.offset().top + cont.scroll - $cont.offset().top;
971					t = $.fn.menuspy.$elems.filter($scrollTo).data('menuspy').min;
972					b = this.$inner.outerHeight() - this.$content.height();
973				} else { // not sidebar
974					$cont = $win;
975					cont = { scroll: $cont.scrollTop() };
976					t = $scrollTo.offset().top;
977					b = $body.outerHeight() - this.winHeight;
978				}
979				t = Math.max(0, t - this.scrollOffset);
980				this.scroll = Math.round(t < b ? t : b);
981				var dur = this.scrollMinDur + Math.abs(cont.scroll - this.scroll) / this.scrollDistDiv;
982				gsap.to(cont, {
983					duration: dur,
984					scroll: this.scroll,
985					onUpdate: function onUpdateScroll() { $cont.scrollTop(cont.scroll) },
986					onComplete: this.popup && this.menuIconCorner && this.winWidth < this.hidePopupUnder
987						? function onEndScroll() {
988							if (!this.$button.eq(1).hasClass(this.closeClass)) return;
989							gsap.set(this.$button[0], {
990								x: this.menuIconX - this.$node.width(),
991								y: this.menuIconY
992							});
993							this.$button.eq(1).addClass("sm-hide");
994							if (!smMobile) this.$node.one("mouseenter", $.proxy(this, "onClickButton"));
995						}.bind(this)
996						: function () {
997							$a.closest('dt').addClass('active').siblings('.active').removeClass('active')
998						}
999				});
1000			} else {  // open url
1001				if (!(smMobile && this.popup)) // fix for mobile popup menu-item opening
1002					if (a == e.target.parentNode || a == e.target) return;
1003				if (!a.click) {
1004					var event = document.createEvent("HTMLEvents");
1005					event.initEvent("click", false, true);
1006					a.dispatchEvent(event);
1007				} else a.click();
1008			}
1009		},
1010
1011		onOpenDrop(e) {
1012			if (this.sidebar && !$body.hasClass(this.openClass)) return;
1013			var item = e.currentTarget,
1014				openTween = item.parentNode.parentNode.openTween,
1015				closeTween = item.parentNode.parentNode.closeTween,
1016				items = item.parentNode.children, i;
1017			for (i = 0; i < items.length; i++)
1018				if (items[i] != item && items[i].menu && items[i].menu.style.display == "block") { this.closeDrop(items[i].menu); break; }
1019			item.menu.closeTimeout = clearTimeout(item.menu.closeTimeout);
1020			if (openTween && openTween.isActive())
1021				openTween.vars.onComplete = $.proxy(this, "openDrop", item);
1022			else if (closeTween && closeTween.isActive())
1023				closeTween.vars.onReverseComplete = $.proxy(this, "openDrop", item);
1024			else
1025				this.openDrop(item);
1026		},
1027
1028		onCloseDrop(e) {
1029			var item = e.currentTarget,
1030				openTween = item.parentNode.parentNode.openTween,
1031				closeTween = item.parentNode.parentNode.closeTween;
1032			if (openTween && openTween.vars.onComplete)
1033				delete openTween.vars.onComplete;
1034			else if (closeTween && closeTween.vars.onReverseComplete)
1035				delete closeTween.vars.onReverseComplete;
1036			else
1037				item.menu.closeTimeout = setTimeout($.proxy(this, "closeDrop", item.menu), this.anim.outDur * 500 || 200);
1038		},
1039
1040		onMouseEnterDrop(e) {
1041			if (this.sidebar && !$body.hasClass(this.openClass)) return;
1042			var menu = e.currentTarget,
1043				item = menu.parentItem;
1044			menu.style.zIndex = 99;
1045			do {
1046				if (menu.closeTween && menu.closeTween.isActive()) menu.closeTween.reverse();
1047				menu.closeTimeout = clearTimeout(menu.closeTimeout);
1048				menu = menu.parent;
1049				$(item).addClass("hover");
1050				item = menu.parentItem;
1051			} while ("closeTimeout" in menu);
1052		},
1053
1054		onMouseLeaveDrop(e) {
1055			var menu = e.currentTarget,
1056				item = menu.parentItem,
1057				items = menu.children[0].children,
1058				i = items.length, t = 1;
1059			while (i--)
1060				if (items[i].menu && items[i].menu.closeTimeout) { t++; break }
1061			do {
1062				menu.closeTimeout = setTimeout($.proxy(this, "closeDrop", menu), (this.anim.outDur * 500 || 200) * t++);
1063				menu = menu.parent;
1064				$(item).removeClass("hover");
1065				item = menu.parentItem;
1066			} while ("closeTimeout" in menu);
1067		},
1068
1069		closeAllDrops() {
1070			var $menu = this[this.sidebar ? "$node" : "$drop"].children(".sm-level[style*=block]:last");
1071			if ($menu.length) this.onMouseLeaveDrop({ currentTarget: $menu[0] });
1072		},
1073
1074		openDrop(item) {
1075			var submenu = item.menu,
1076				openTween = item.parentNode.parentNode.openTween,
1077				closeTween = item.parentNode.parentNode.closeTween;
1078			if (openTween) delete openTween.vars.onComplete;
1079			if (closeTween) delete closeTween.vars.onReverseComplete;
1080			// replace nodes to be on top (zIndex)
1081			if (this.sidebar && submenu.parentNode != this.$node[0])
1082				$(submenu).appendTo(this.$node);
1083			if (!this.sidebar && submenu.parentNode != this.$drop[0])
1084				$(submenu).appendTo(this.$drop);
1085
1086			var p = $(item).offset(),
1087				w = $(item).outerWidth(),
1088				h = $(submenu).outerHeight(),
1089				t = $win.scrollTop(),
1090				m = t + this.winHeight - p.top - h;
1091			if (submenu.parent.parentNode == this.$lvls[0])
1092				this.dropdir = p.left + w / 2 - this.winWidth / 2 < 0 ? 1 : -1;
1093
1094			if (this.dropdir < 0) {
1095				p.right = $win.outerWidth() - p.left; // don't use this.winWidth
1096				if (this.sidebar && p.right < this.width) p.right = this.width - this.menuPadding;
1097				submenu.style.right = p.right + this.menuPadding + this.dropspace + "px";
1098				submenu.style.left = "";
1099			} else {
1100				if (p.left < 0) p.left = 0;
1101				submenu.style.left = w + p.left + this.menuPadding + this.dropspace + "px";
1102				submenu.style.right = "";
1103			}
1104
1105			if (this.dropFullHeight) {
1106				p.top = t;
1107				submenu.style.height = "100vh";
1108			} else if (m < 0) { // out of screen
1109				var ih = $(item).outerHeight(), y = p.top;
1110				p.top += Math.floor(m / ih) * ih;
1111				if (p.top + h <= y) p.top += ih;
1112				if (p.top < t) p.top = t;
1113				submenu.children[0].style.height = h >= this.winHeight ? "100vh" : "";
1114			}
1115			if (this.effect < 0) p.top -= $win.scrollTop(); // reduce_width bugfix (#21)
1116			submenu.style.top = p.top - (this.dropFullHeight ? 0 : this.menuPadding) + "px";
1117			submenu.style.zIndex = 99;
1118
1119			this.anim.inCSS.x = this.anim.inUnitX == "%" ? this.anim.inX * this.dropwidth / 100 : this.anim.inX;
1120			this.anim.inCSS.x *= this.dropdir;
1121			this.anim.inCSS.rotationY = this.dropdir * this.anim.inRotationY;
1122			gsap.set(submenu, {
1123				transformPerspective: this.anim.perspective,
1124				transformOrigin: Hlpr.calcOrigin(this.anim.inOrigin, this.dropwidth, this.dropdir)
1125			});
1126			var animTo = {
1127				duration: this.anim.inDur,
1128				overwrite: 1,
1129				css: this.baseCSS,
1130				ease: this.anim.inEase
1131			};
1132			if (submenu.style.display == "none") {
1133				submenu.openTween = gsap.fromTo(submenu, this.anim.inCSS, animTo);
1134				// menu-item anim
1135				if (this.miAnim) {
1136					this.miCSS.x = this.miUnitX == "%" ? this.miX * w / 100 : this.miX;
1137					this.miCSS.x *= this.dropdir;
1138					this.miCSS.rotationY = this.miRotationY * this.dropdir;
1139					if (this.miShift) {
1140						gsap.fromTo($(">dl>dt", submenu), this.miCSS, {
1141							duration: this.miDur,
1142							overwrite: 1,
1143							css: this.baseCSS,
1144							ease: this.miEase,
1145							stagger: this.miShift
1146						});
1147					} else {
1148						gsap.fromTo(submenu.children[0], this.miCSS, {
1149							duration: this.miDur,
1150							overwrite: 1,
1151							css: this.baseCSS,
1152							ease: this.miEase
1153						});
1154					}
1155				}
1156				// icons anim
1157				if (this.iconAnim && !smMobile)
1158					gsap.fromTo($(".sm-icon img", submenu), { scale: 0, opacity: 0 }, {
1159						duration: 0.5,
1160						css: { scale: 1, opacity: 1 },
1161						ease: "back.out",
1162						delay: 0.66 * this.dur
1163					});
1164			} else {
1165				submenu.openTween = gsap.to(submenu, animTo);
1166			}
1167
1168			submenu.style.display = "block";
1169			this.updateScrollbar(submenu);
1170			$(item).trigger("openParentItem", this);
1171		},
1172
1173		closeDrop(submenu) {
1174			if (submenu.style.display == 'none') return;
1175			submenu.style.zIndex = "";
1176			this.anim.outCSS.x = this.anim.outUnitX == "%" ? this.anim.outX * this.dropwidth / 100 : this.anim.outX;
1177			this.anim.outCSS.rotationY = this.dropdir * this.anim.outRotationY;
1178			gsap.set(submenu, { transformOrigin: Hlpr.calcOrigin(this.anim.outOrigin, this.dropwidth, this.dropdir) });
1179			submenu.closeTween = gsap.to(submenu, {
1180				duration: this.anim.outDur,
1181				overwrite: 1,
1182				css: this.anim.outCSS,
1183				ease: this.anim.outEase,
1184				onComplete: Target.hide
1185			});
1186		},
1187
1188		onClickTree(e) {
1189			e.stopPropagation();
1190			e.preventDefault();
1191			//if (smMobile && e.type == 'click') return;
1192			var $item = $(e.currentTarget);
1193			this[$item.hasClass("opened") ? "closeTree" : "openTree"]($item);
1194		},
1195
1196		openTree($item) {
1197			var dur = this.dur;
1198			var $dd = $item.addClass("opened").next();
1199			if (this.popup) this.$lvls.height("auto");
1200			if (this.accordion) {
1201				$item.siblings("dt.opened").removeClass("opened");
1202				$dd.siblings("dd.opened").removeClass("opened").slideUp({ easing: this.outEase, duration: dur * 1000 });
1203			}
1204			$dd.slideDown({
1205				queue: false,
1206				easing: this.inEase,
1207				duration: dur * 1000,
1208				progress: this.bindUpdateScrollbar,
1209				complete: function () { $dd.attr('style', 'display: block;') }
1210			}).addClass('opened');
1211
1212			if (this.miAnim) {
1213				this.miCSS.x = this.miUnitX == "%" ? this.miX * this.$lvls.outerWidth() / 100 : this.miX;
1214				if (this.miShift) {
1215					dur = gsap.timeline().fromTo($dd.find("dt").not("dd:not(.opened) dt"), this.miCSS, {
1216						duration: this.miDur,
1217						overwrite: 1,
1218						css: this.baseCSS,
1219						ease: this.miEase,
1220						stagger: this.miShift
1221					}).duration();
1222				} else {
1223					gsap.fromTo($dd.find("dl").not("dd:not(.opened) dl"), this.miCSS, {
1224						duration: this.miDur,
1225						overwrite: 1,
1226						css: this.baseCSS,
1227						ease: this.miEase
1228					});
1229				}
1230			}
1231
1232			// icons anim
1233			if (this.iconAnim && !smMobile) {
1234				gsap.fromTo($dd.find(".sm-icon img"), { scale: 0, opacity: 0 }, {
1235					duration: 0.5,
1236					css: { scale: 1, opacity: 1 },
1237					ease: "back.out",
1238					delay: 0.66 * this.dur
1239				});
1240			}
1241			$item.trigger("openParentItem", this);
1242		},
1243
1244		closeTree($item) {
1245			var $dd = $item.removeClass("opened").next();
1246			$dd.removeClass('opened').slideUp({
1247				queue: false,
1248				easing: this.outEase,
1249				duration: this.dur * 1000,
1250				progress: this.bindUpdateScrollbar,
1251				complete: function () { $dd.attr('style', '') }
1252			});
1253		},
1254
1255		onOpen(e) {
1256			if (this.drop) return;
1257			if (e.type == "touchend") e.preventDefault();
1258			if (e.currentTarget.parentNode.parentNode == this.prev) return;
1259			if (this.disableClick) return; // disable click during swipe
1260			var title = $(e.currentTarget).find("a").html().replace(/<.*>/, "");
1261			this.open(title, e.currentTarget.menu);
1262		},
1263
1264		open(title, submenu) {
1265			if (submenu.parentNode != this.$lvls[0])
1266				$(submenu).css({ top: '', left: '', right: '' }).appendTo(this.$lvls);
1267			var w = this.$lvls.outerWidth(),
1268				tl = gsap.timeline({ paused: true, autoRemoveChildren: true, onComplete: $.proxy(this, "onEndSlide") });
1269			gsap.set(this.opened, { transformOrigin: Hlpr.calcOrigin(this.outOrigin, w) });
1270			gsap.set(submenu, { transformOrigin: Hlpr.calcOrigin(this.inOrigin, w) });
1271
1272			this.inCSS.x = this.inUnitX == "%" ? this.inX * w / 100 : this.inX;
1273			this.outCSS.x = this.outUnitX == "%" ? this.outX * w / 100 : this.outX;
1274
1275			// submenu anims
1276			tl.add(gsap.to(this.opened, {
1277				duration: this.dur,
1278				css: this.outCSS,
1279				ease: this.outEase,
1280				onComplete: Target.scrollUp
1281			}), 0);
1282			tl.add(gsap.fromTo(submenu, this.inCSS, {
1283				duration: this.dur,
1284				css: this.baseCSS,
1285				ease: this.inEase
1286			}), 0);
1287
1288			this.opened.style.zIndex = "";
1289			submenu.style.zIndex = 99;
1290			submenu.style.display = "block";
1291			if (!this.sidebar && this.winWidth >= this.sidebarUnder) {
1292				if (this.popup) submenu.children[0].style.maxHeight = this.$lvls[0].style.maxHeight;
1293				var h = $(submenu.children[0]).outerHeight() + 2 * this.menuPadding;
1294				tl.add(gsap.to(this.$lvls[0], { duration: this.dur, height: h }), 0);
1295			}
1296
1297			if (!$(this.opened.children).hasClass("sm-result")) { // if filtering was started at search result, don't run it
1298				// header anims
1299				if (this.$head.length) {
1300					var $title = $('<span class="sm-title" title="' + title + '">' + title + '</span>').appendTo(this.$head),
1301						i = this.$title.length;
1302					if (!this.$title[0].style.position) { // init title position
1303						gsap.set(this.$title[0], { position: "absolute", x: this.$title._center(this.$back.length) });
1304					}
1305					if (this.$back.length) { // clean theme
1306						if (i == 1) {
1307							tl.add(gsap.to(this.$back[0], {
1308								duration: this.dur,
1309								css: { opacity: 1, display: 'block' }
1310							}), 0);
1311						}
1312						if (this.$title[--i]) {
1313							this.$title[i].style.position = "absolute";
1314							tl.add(gsap.to(this.$title[i], {
1315								duration: 2 * this.dur,
1316								css: { x: -w * 0.6, opacity: -1, scale: 0 }
1317							}), 0);
1318						}
1319					} else { // flat theme
1320						this.$title.attr("class", "sm-back");
1321						if (this.$title[--i]) {
1322							this.$title[i].style.position = "absolute";
1323							tl.add(gsap.to(this.$title[i], { duration: this.dur, x: 0, scale: backScale }), 0);
1324						}
1325						if (this.$title[--i]) {
1326							tl.add(gsap.to(this.$title[i], { duration: this.dur, x: -w * 0.6, ease: "none" }), 0);
1327						}
1328					}
1329					tl.add(gsap.fromTo($title, { x: w }, { duration: this.dur, x: $title._center(this.$back.length) }), 0);
1330					this.$title.push($title[0]);
1331				}
1332
1333				// bg anim
1334				if (this.bg && this.bgX) {
1335					var level = this.opened.children[0].attributes['class'].value.match(/\blevel(\d)\b/)[1];
1336					tl.add(gsap.to(this.$node[0], { duration: this.dur, backgroundPosition: this.bgX * level + "% 0" }), 0);
1337				}
1338			}
1339			// menu-item anim
1340			if (this.miAnim) {
1341				this.miCSS.x = this.miUnitX == "%" ? this.miX * w / 100 : this.miX;
1342				if (this.miShift) {
1343					tl.fromTo($(">dl>dt", submenu), this.miCSS, {
1344						duration: this.miDur,
1345						css: this.baseCSS,
1346						ease: this.miEase,
1347						stagger: this.miShift
1348					}, 0);
1349				} else {
1350					tl.fromTo(submenu.children[0], this.miCSS, {
1351						duration: this.miDur,
1352						css: this.baseCSS,
1353						ease: this.miEase
1354					}, 0);
1355				}
1356			}
1357			// icons anim
1358			if (this.iconAnim && !smMobile) {
1359				tl.add(gsap.fromTo($(".sm-icon img", submenu), { scale: 0, opacity: 0 }, {
1360					duration: 0.5,
1361					css: { scale: 1, opacity: 1 },
1362					ease: "back.out"
1363				}), 0.66 * this.dur);
1364			}
1365			this.openTL = tl;
1366
1367			if (this.backTL && this.backTL._first) delete this.backTL._first.vars.onComplete; // don't run Target.scrollUp
1368			gsap.set(this.$lvls[0], { perspective: this.perspective }); // blur fix for retina
1369			this.openTL.play();
1370			this.prev = this.opened;
1371			this.opened = submenu;
1372			this.updateScrollbar();
1373
1374			// don't trigger event when submenu is search result
1375			if (!$(submenu.children[0]).hasClass("sm-result")) $(submenu.parentItem).trigger("openParentItem", [this, title]);
1376		},
1377
1378		onResetFilter() {
1379			this.$filter.attr("value", "").val("");
1380			this.$reset.attr('tabindex', -1);
1381			if ($(this.opened.children).hasClass("sm-result")) this.onBack();
1382			this.$filter[0].focus();
1383		},
1384
1385		onBack(e) {
1386			if (e && e.type == "touchend") e.preventDefault();
1387			if (!this.opened.parent) return;
1388			this.initBackTimeline();
1389			this.back();
1390		},
1391
1392		back() {
1393			if (this.openTL && this.openTL._first) delete this.openTL._first.vars.onComplete; // don't run Target.scrollUp
1394			gsap.set(this.$lvls[0], { perspective: this.perspective }); // blur fix for retina
1395			this.backTL.resume();
1396			this.prev = this.opened;
1397			this.opened = this.opened.parent;
1398			this.updateScrollbar();
1399
1400			this.$title.length--;
1401			this.$filter.attr("value", "");
1402			this.keyword = "";
1403			$(this.opened).trigger("back", this);
1404		},
1405
1406		initBackTimeline() {
1407			var w = this.$lvls.addClass("sm-swipe").outerWidth(),
1408				tl = gsap.timeline({
1409					paused: true,
1410					autoRemoveChildren: true,
1411					onComplete: $.proxy(this, "onEndSlide"),
1412					onReverseComplete: function () {
1413						this.onEndSlide();
1414						this.opened.parent.style.display = "none";
1415					}.bind(this)
1416				});
1417
1418			this.inCSS.x = this.inUnitX == "%" ? this.inX * w / 100 : this.inX;
1419
1420			gsap.set(this.opened, { transformOrigin: Hlpr.calcOrigin(this.inOrigin, w) });
1421			gsap.set(this.opened.parent, { transformOrigin: Hlpr.calcOrigin(this.outOrigin, w) });
1422
1423			// submenu anims
1424			tl.add(gsap.to(this.opened, { duration: this.dur, css: this.inCSS, onComplete: Target.scrollUp }), 0);
1425			tl.add(gsap.to(this.opened.parent, { duration: this.dur, css: this.baseCSS, ease: this.inEase }), 0);
1426
1427			this.opened.parent.style.display = "block";
1428			if (!this.sidebar && this.winWidth >= this.sidebarUnder) {
1429				if (this.popup) this.opened.parent.children[0].style.maxHeight = this.$lvls[0].style.maxHeight;
1430				tl.add(gsap.to(this.$lvls[0], {
1431					duration: this.dur,
1432					css: { height: $(this.opened.parent.children[0]).outerHeight() + 2 * this.menuPadding },
1433					onComplete: this.prev.parentNode.children[0] == this.prev ? Target.autoHeight : undefined
1434				}), 0);
1435			}
1436
1437			// header anims
1438			if (this.$head.length) {
1439				var i = this.$title.length;
1440				tl.add(gsap.to(this.$title[--i], {
1441					duration: this.dur,
1442					css: { x: w, opacity: 0 },
1443					onComplete: Target.remove
1444				}), 0);
1445				if (this.$back.length) { // clean theme
1446					if (i == 1) tl.add(gsap.to(this.$back[0], {
1447						duration: this.dur,
1448						css: { opacity: 0, display: 'none' }
1449					}), 0);
1450					tl.add(gsap.to(this.$title[--i], {
1451						duration: this.dur,
1452						css: { x: $(this.$title[i])._center(true), opacity: 1, scale: 1 }
1453					}), 0);
1454				} else { // flat theme
1455					tl.add(gsap.to(this.$title[--i], {
1456						duration: this.dur,
1457						css: { x: $(this.$title[i])._center(), scale: 1, maxWidth: i ? this.titleWidth : "100%" },
1458						onComplete: Target.autoMaxWidth
1459					}), 0);
1460					if (this.$title[--i]) tl.add(gsap.to(this.$title[i], { duration: this.dur, x: 0 }), 0);
1461				}
1462			}
1463
1464			// bg anim
1465			if (this.bg && this.bgX) {
1466				var level = this.opened.children[0].attributes['class'].value.match(/\blevel(\d)\b/);
1467				level = level ? level[1] - 2 : 0;
1468				tl.add(gsap.to(this.$node[0], { duration: this.dur, backgroundPosition: this.bgX * level + "% 0" }), 0);
1469			}
1470
1471			this.backTL = tl;
1472		},
1473
1474		onEndSlide() {
1475			// blur fix for retina
1476			$(this.opened).css({ WebkitTransform: "", MozTransform: "", msTransform: "", transform: "" });
1477			this.$lvls.css({ WebkitPerspective: "", MozPerspective: "", perspective: "" }).removeClass("sm-swipe");
1478			this.disableClick = false; // enable click after swipe
1479		},
1480
1481		updateScrollbar(menu) {
1482			if (smMobile) return;
1483			var $dl = $((menu || this.opened).children[0]);
1484			$dl.perfectScrollbar($dl.data("perfectScrollbar") ? "update" : undefined);
1485		}
1486
1487	};
1488
1489	// depreciated functions
1490	VerticalSlideMenu.prototype.openPopup = VerticalSlideMenu.prototype.openMenu;
1491	VerticalSlideMenu.prototype.closePopup = VerticalSlideMenu.prototype.closeMenu;
1492	VerticalSlideMenu.prototype.openSidebar = VerticalSlideMenu.prototype.openMenu;
1493	VerticalSlideMenu.prototype.closeSidebar = VerticalSlideMenu.prototype.closeMenu;
1494
1495	$html.on('click.sm', '.sm-toggle', function (e) {
1496		var id = this.className.match(/\bsm-(\d+)\b/);
1497		id = id ? id[1] : ($('.sm-menu:first').attr('id') || '').split('_')[1];
1498		var sm = window['sm' + id];
1499		if (sm) {
1500			e.preventDefault();
1501			sm.$cont.hasClass(sm.openClass) ? sm.closeMenu(e) : sm.openMenu(e);
1502		}
1503	});
1504
1505	(function initMenuSpy() {
1506		var $elems = $();
1507		$win
1508			.on("load.menuspy resize.menuspy", onResize)
1509			.on("scroll.menuspy", onScroll)
1510		;
1511
1512		function onScroll() {
1513			if (!$elems.length) return;
1514			var y = $win.scrollTop();
1515			$elems.each(function (i) {
1516				var $elem = $(this);
1517				var o = $elem.data('menuspy');
1518				if (y >= o.min && y < o.max) {
1519					if (!o.inside) {
1520						o.inside = true;
1521						$elem.trigger("scrollEnter.menuspy", { scrollTop: y, enters: ++o.enters, leaves: o.leaves });
1522					}
1523				} else if (o.inside) {
1524					o.inside = false;
1525					$elem.trigger("scrollLeave.menuspy", { scrollTop: y, enters: o.enters, leaves: ++o.leaves });
1526				}
1527			});
1528		}
1529
1530		function onResize() {
1531			$elems.each(function () {
1532				var $elem = $(this);
1533				var o = $elem.data("menuspy");
1534				o.min = $elem.offset().top;
1535				o.max = $elem.outerHeight() + o.min;
1536			});
1537			onScroll();
1538		}
1539
1540		$.fn.menuspy = function (options) {
1541			var defaults = {
1542				offset: 0,
1543				enters: 0,
1544				leaves: 0,
1545				inside: false
1546			};
1547			return this.each(function () {
1548				var $elem = $(this);
1549				var top = $elem.offset().top;
1550				$elem.data("menuspy", $.extend({
1551					min: top,
1552					max: top + $elem.outerHeight(),
1553				}, defaults, options));
1554				$elems.filter(this).length || $elems.push(this);
1555			});
1556		};
1557
1558		$.fn.menuspy.$elems = $elems;
1559	})();
1560
1561})(window.jq183 || jQuery);

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.