PageSourceSearch

https://hotel-kindl.at/static/template-2317/js/site.js

js hotel-kindl.at collected 2026-09-26 18:55:46 UTC 41,025 bytes, 1,223 lines download raw bytes

1!(function ( $, me, w, d ) {
2    "use strict";
3    if ( !$ || !me ) {
4        return;
5    }
6
7    if ( d.location.href.indexOf( '.vioma.de' ) > -1 ) {
8        w.me = me;
9    }
10
11	// Overwrite Modernizr so no time is wasted on some old legacy checks.
12	window.Modernizr = {
13		touchevents: 			('ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0),
14		intersectionobserver:	('IntersectionObserver' in window || 'IntersectionObserverEntry' in window || 'intersectionRatio' in window.IntersectionObserverEntry.prototype),
15	}
16
17    
18    me.custom_events = function () {
19        // customer functions here
20    }
21
22    me.init();
23
24    me.tmp = {
25        // tmp is for storing temporal stuff.
26        widgetYear: {}
27    };
28
29    me.vanilla = {
30		site:	d.querySelector("#site"),
31		header:	d.querySelector("header"),
32		main: 	d.querySelector("main"),
33        widget: d.querySelector(".widget-form-book"),
34		footer: d.querySelector("footer")
35    }
36
37	me.opt.bgChangeElements 		= me.vanilla.main.querySelectorAll(":scope > .grp-bg-dark-smooth, article[data-attr-hintergrund='dark-smooth']"),
38	me.opt.roundImageTextBoxSVGs 	= me.vanilla.main.querySelectorAll("figure.image-text-box-figure > svg");
39
40	me.opt.mails 					= me.vanilla.site.querySelectorAll("a[data-mail]");
41	me.vanilla.layout 				= me.vanilla.site.querySelector(":scope > #layout-vanilla");
42
43	me.opt.touch 					= window.Modernizr.touchevents;
44
45	me.vanilla.site.classList.remove("no-js"),
46	me.vanilla.site.classList.add("js-active");
47
48	me.opt.globalToggleMenu = me.vanilla.site.querySelector(":scope > #global-toggle-menu"),
49	me.opt.globalToggleWidget = me.vanilla.site.querySelector(":scope > #global-toggle-widget");
50
51	me.opt.scroll_breakpoint = () => {
52		if (me.vanilla.header.querySelector("[data-smallheader]")) {
53			return (me.opt.scroll >= me.opt.main_top * 0.02);
54		};
55		return (me.opt.scroll >= me.opt.main_top * 0.075);
56	};
57
58	me.scroll_event = function () {
59		me.opt.site.toggleClass( 'scrolled', me.opt.scroll_breakpoint() );
60		if ((me.opt.already_scrolled == false) && (me.opt.scroll_breakpoint() == true)) {
61			me.opt.site.addClass("first-scroll");
62			me.opt.noscript_afterload.each(function() {
63				var i = $(this)
64				me.opt.head.append(i.text());
65				i.remove();
66			});
67			me.opt.already_scrolled = true;
68		}
69	};
70
71	me.opt.scrollBarVisibility = () => {
72		const upScrollDelayPixels = 100;
73
74		if (me.tmp.windowOffsetY > window.scrollY) {
75			me.tmp.scrolledTopDirection = me.tmp.scrolledTopDirection + (me.tmp.windowOffsetY - window.scrollY);
76		}
77		else {
78			me.tmp.scrolledTopDirection = 0;
79		}
80
81		if (me.tmp.scrolledTopDirection > upScrollDelayPixels) {
82			me.vanilla.site.classList.add("scrolled-upwards");
83		}
84		else {
85			me.vanilla.site.classList.remove("scrolled-upwards");
86		}
87
88		// Now reset offset variable
89		me.tmp.windowOffsetY = window.scrollY;
90	}
91
92	// CSS :has() Fallback Support
93	if (!CSS.supports("selector(html:has(body))")) {
94		me.vanilla.site.classList.add("no-css-has"),
95		me.vanilla.site.classList.remove("supports-css-has");
96
97		// .media-display-small only exists in main with group layout = Spalten!
98		// Add fallback class so stuff can be applied on article and media
99		me.vanilla.main.querySelectorAll(".media-display-small").forEach(md => {
100			md.closest(".grp-layout-spalten").classList.add("has-fallback-article-md");
101		});
102
103		me.opt.customSmallGallery = me.vanilla.header.querySelector(".custom-gallery-main[data-smallheader='true']");
104
105		if (me.opt.customSmallGallery) {
106			
107			me.vanilla.header.querySelector(".custom-gallery").classList.add("has-fallback-smallheader-true");
108		}
109
110		let smallHeader = me.vanilla.header.querySelector("[data-smallheader='absolute']");
111
112		if (w.innerWidth <= 639 && smallHeader && smallHeader.previousElementSibling && smallHeader.previousElementSibling.classList.contains("familotel-label")) {
113			smallHeader.previousElementSibling.style.setProperty("top", "75px");
114		}
115	}
116
117	me.f = {
118		// f = functions
119		
120		visibilityClassToggler: (el, className, once, options) => {
121            // el 	 		give single element with querySelector or multiple via querySelectorAll or text str
121ing, like ".mosaic-item"
122            // className  	the name of the visibility class, usually .visible,
123            // once  		mode.
124                			// mode true  => class will always be added and removed again, and added and removed, ...
125                			// mode false/not given
126			// options	 	some options like rootMargin
127
128			// There is a "hidden" support for pseudo media queries. Give this to el:
129			// el._ioMinWidth = 640 	==> Applies only if screen width is greater or exact this value, <= 640
130			// el._ioMaxWidth = 639 	==> Applies only if screen width is greater or exact this value, >= 639
131
132            if (!el) return false;
133            className = className || "visible";
134
135			options = options || {
136				rootMargin: "-20% 0% -20% 0%"
137			};
138
139			if (me.opt.userPrefers.reducedMotion) {
140				el.classList.add(className);
141				return;
142			}
143
144			let video = el.querySelector("video");
145
146            let classTogglerIO = new IntersectionObserver((entries) => {
147                entries.forEach(entry => {
148					let target = entry.target;
149						
150					if 	(entry.isIntersecting && 
151							((!target._ioMinWidth || target._ioMinWidth && w.innerWidth >= target._ioMinWidth ) &&
152							(!target._ioMaxWidth || target._ioMaxWidth && w.innerWidth <= target._ioMaxWidth ))
153						) {
154						target.classList.add(className);
155
156						if (video && video.dataset.allowautoplay == "true") {
157							video.play();
158							
159							if (!video.controls) {
160								video.addEventListener("ended", () => {
161									video.play();
162								});
163							}
164						}
165
166						if (once) classTogglerIO.unobserve(target);
167
168						// Special for background color switch
169						if (className == 'darken-page') {
170							me.opt.roundImageTextBoxSVGs.forEach(svg => {
171								svg.setAttribute("fill", "#222c36");
172							});
173						}
174
175
176						
177					}
178					else {
179						entry.target.classList.remove(className);
180
181						if (video && video.dataset.allowautoplay == "true") {
182							video.pause();
183						}
184
185						// Special for background color switch
186						if (className == 'darken-page') {
187							me.opt.roundImageTextBoxSVGs.forEach(svg => {
188								svg.setAttribute("fill", "#4c5b69");
189							});
190						}
191					}
192                });
193            }, options);
194
195            if (typeof el == "string") {
196                d.querySelectorAll(el).forEach(elem => {
197                    classTogglerIO.observe(elem);
198                });
199            }
200            else if (el.nodeName) {
201                classTogglerIO.observe(el);
202            }
203            else if (el[0] && el[0].nodeName) {
204                el.forEach(elem => {
205                    classTogglerIO.observe(elem);
206                });
207            }
208        },
209
210		resizeObserver: () => {
211			if (!me.vanilla.layout) {
212				return false;
213			}
214			var newMqEvent = new CustomEvent("mq-resize");
215			const layoutResizeObserver = new ResizeObserver(entries => {
216				entries.forEach(entry => {
217					me.vanilla.layout._width = entry.contentRect.width;
218					me.vanilla.layout._portrait = window.innerHeight > window.innerWidth;
219					window.dispatchEvent(newMqEvent);
220				});
221			});
222			layoutResizeObserver.observe(me.vanilla.layout);
223		},
224
225		packageCardsFilter: (container) => {
226			container = typeof container == "object" ? container : d.querySelector(container);
227			if (!container) return;
228
229			container._filters = {
230				category: "",
231				month: ""
232			},
233			container._filterButtons = container.querySelectorAll(":scope > .condeon-cst-list-filterlist button"),
234			container._cards = container.querySelectorAll(":scope > .condeon-cst-list-items > .small-itemslider-item");
235
236
237			// Main filter function for both indicator and months list
238			container._filterTheList = (e) => {
239				const btn = e.target.closest("button");
240				const filterType = btn.dataset.id ? "category" : "month";
241
242				if (filterType == "category") {
243					container._filters.category = btn.dataset.id && btn.dataset.id == "all" ? "" : btn.dataset.id || container._filters.category;
244				}
245				else {
246					container._filters.month = btn.dataset.month && btn.dataset.month == "all" ? "" : btn.dataset.month || container._filters.month;
247				}
248
249				// Toggle cards themselfes
250				container._cards.forEach(card => {
251					card._i = 0;
252					Object.values(container._filters).forEach(filterValue => {
253						if (!filterValue || filterValue == "" || filterValue == "all") {
254							card._i++;
255						}
256						else if (
257							(card.dataset.indicators && card.dataset.indicators.includes(filterValue)) ||
258							(card.dataset.months && card.dataset.months.includes(filterValue))
259						) {
260							card._i++;
261						}
262					});
263					if (card._i == 2) {
264						card.classList.remove("filtered-out");
265					}
266					else {
267						card.classList.add("filtered-out")
268					}
269				});
270
271				// If monthly filter, close dorpdown
272				if (filterType == "month") {
273					btn.closest(".condeon-cst-list-filterlist").querySelector(".condeon-cst-list-monthtoggle").checked = false;
274					
275					let dropdownLabel = btn.closest(".pseudo-dropdown").querySelector("label");
276					if (!dropdownLabel.style.minWidth) {
277						dropdownLabel.style.minWidth = dropdownLabel.clientWidth + "px";
278					}
279
280					dropdownLabel.textContent = btn.textContent;
281				}
282
283				// If done, disable all filters which would return a zero results list
284
285				container._filterButtons.forEach(button => {
286
287					if ((button.dataset.id && button.dataset.id == "all") || 
288						(button.dataset.month && button.dataset.month == "all")) 
289						return;
290
291					if (button.dataset.id && button.dataset.id > 0) {
292						var hasVisibleCard = container.querySelector(".small-itemslider-item[data-indicators*='" + button.dataset.id + "']:not(.filtered-out)");
293						button.disabled = !hasVisibleCard;
294					}
295
296					else if (button.dataset.month && button.dataset.month > 0) {
297						console.log();
298						var cat =  container._filters.category || "";
299						var stillEnabled = !!container._filters.category ?
300											container.querySelector(".small-itemslider-item[data-indicators*='" + container._filters.category + "'][data-months*='" + button.dataset.month + "']") :
301											container.querySelector(".small-itemslider-item[data-months*='" + button.dataset.month + "']");
302						button.disabled = !stillEnabled;
303					}
304				});
305
306				/* container._filterButtons.forEach(button => {
307
308					if ((button.dataset.id && button.dataset.id == "all") || (button.dataset.month && button.dataset.month == "all")) return;
309
310					else if (button.dataset.month && button.dataset.month > 0) {
311						var hasVisibleCard = container.querySelector(".small-itemslider-item[data-months*='" + button.dataset.month + "']:not(.filtered-out)");						
312						button.disabled = !hasVisibleCard;
313					}
314					else if (button.dataset.id && button.dataset.id > 0) {
315						var hasVisibleCard = container.querySelector(".small-itemslider-item[data-indicators*='" + button.dataset.id + "']:not(.filtered-out)");						
316						button.disabled = !hasVisibleCard;
317					} 
318				}); */
319			}
320
321			// Apply click event on filter buttons
322			container._filterButtons.forEach(btn => {
323				btn.addEventListener("click", container._filterTheList, {passive: true});
324			});
325
326			// Done here with filters, now apply visible class adder for the cards as well
327			container.querySelectorAll("div.small-itemslider-item").forEach(item => {
328				me.f.visibilityClassToggler(item, false, false, {
329					rootMargin: "-5% 0% -5% 0%"
330				});
331			})
332		},
333
334		packageCardsRegular: (container) => {
335			container = typeof container == "object" ? container : d.querySelector(container);
336
337			if (!container) return;
338
339			container.querySelectorAll("div.small-itemslider-item").forEach(item => {
340				me.f.visibilityClassToggler(item, false, true, {
341					rootMargin: "-20% 0% -20% 0%"
342				});
343			})
344		}
345	}
346
347	me.f.mailificator = (e) => {
348
349		let mailIO = new IntersectionObserver(entries => {
350			entries.forEach(entry => {
351				if (entry.isIntersecting) {
352					entry.target.textContent = entry.target._address;
353					mailIO.unobserve(entry.target);
354				}
355			})
356		})
357
358		me.opt.mails.forEach(m => {
359			m._address = m.dataset.mail + atob("QGhvdGVsLWtpbmRsLmF0");
360
361			mailIO.observe(m);
362			
363			m.addEventListener("click", (e) => {
364				e.preventDefault();
365				w.location.href = (atob("bWFpbHRvOg") + m._address);
366			});
367		})
368	}
369
370	me.f.mobileNumberficator = () => {
371		if (me.opt.touch) {
372			me.opt.mobileNumbers = me.vanilla.site.querySelectorAll("a[data-mobile]");
373
374			me.opt.mobileNumbers.forEach(link => {
375				link.addEventListener("click", (e) => {
376					e.preventDefault();
377					w.location.href = link.dataset.mobile;
378				});
379			})
380		}
381	}
382
383	me.f.footerSlider = (el) => {
384		el = el.id ? el : me.vanilla.footer.querySelector("#" + el);
385
386		if (!el) return;
387
388		el._prevButton  = el.querySelector("button.slide-arrow-left"),
389		el._nextButton  = el.querySelector("button.slide-arrow-right"),
390		el._itemPos 	= 0,
391		el._slideItems  = el.querySelectorAll(".footer-logoslider-slide");
392
393		if (el._slideItems.length > 1) {
394
395			let slideFunction = (pos) => {
396
397				if (!el._initialized) {
398					var track = el.querySelector(".footer-logoslider-main");
399					if (track) {
400						track.scrollLeft = 0,
401						track.style.overflow = "hidden";
402					}
403					el._initialized = true;
404				}
405
406				// preload images as soon as user clicks an arrow
407				if (!el._slideActive) {
408					el.querySelectorAll("img").forEach(img => {
409						img.loading = "eager";
410					})
411					el._slideActive = true;
412				}
413
414				let newPos = el._itemPos + pos,
415				skip = el.clientWidth >= 805 ? 4 : el.clientWidth >= 600 ? 3 : el.clientWidth >= 395 ? 2 : 1;
416
417				if (newPos < 0 ) return; // Nicht nach links wenn Position Start wäre
418				if (newPos > el._slideItems.length - skip) return;
419				
420
421				el._slideItems.forEach(item => {
422					item.style.transform = "translateX(calc(-" + (100 * newPos) + "% - " + (15 * newPos) + "px))";
423				});
424
425				el._itemPos = newPos;
426			}
427
428			el._nextButton.addEventListener("click", () => {
429				slideFunction(1);
430			}, {passive: true});
431
432			el._prevButton.addEventListener("click", () => {
433				slideFunction(-1);
434			}, {passive: true});
435		}
436		else {
437			el._arrowPrev.remove(),
438			el._arrowNext.remove();
439		}
440	}
441
442	me.f.mediaDisplayGrid = (el) => {
443		el = el && el.id ? el : d.getElementById(el);
444
445		if (!el) return;
446
447		let video = el.querySelector("video, img");
448
449		if (me.opt.touch) {
450			me.f.visibilityClassToggler(el, 'wide-media-mobile', false, {
451				rootMargin: "-50% 0% -50% 0%"
452			});
453		}
454
455		!me.opt.touch && video && video.addEventListener("click", () => {
456			el.classList.toggle("wide-media");
457			if (video.nodeName == "VIDEO") video.play();
458			el._hoverActive = !el._hoverActive;
459		}, {passive: true});
460
461		!me.opt.touch && video && el.addEventListener("mouseenter", () => {
462			if (video.nodeName == "VIDEO" && !el._videoPlayed && !el.classList.contains("wide-media")) {
463				video.play();
464			}
465			el._hoverActive = true;
466			setTimeout(() => {
467				if (el._hoverActive) {
468					el.classList.add("wide-media")
469				}
470			}, 1500);
471		}, {passive: true});
472
473		!me.opt.touch && video && el.addEventListener("mouseleave", () => {
474			el._hoverActive = false;
475		});
476
477		video && video.nodeName == "VIDEO" && video.addEventListener("ended", () => {
478			el._videoPlayed = true;
479			el.classList.remove("wide-media");
480		}, {passive: true});
481	}
482
483	me.f.masonrier = (container, itemClass) => {
484		container = container.id ? container : d.getElementById(container);
485		if (!container) return;
486
487		container._items = container.querySelectorAll("." + itemClass);
488		if (!container._items || container._items.length <= 1) return;
489
490		if (!CSS.supports("display: grid-lanes")) {
491			var size = 0,
492			timeout = 0,
493			setTimer = null;
494
495			function masonryFunction() {
496                if (window.innerWidth >= 640) {
497                    container._rowHeight = parseInt(window.getComputedStyle(container).getPropertyValue('grid-auto-rows')),
498                    container._rowGap = parseInt(window.getComputedStyle(container).getPropertyValue('grid-row-gap'));
499
500                    for (let x = 0; x < container._items.length; x++){
501                        resizeGridItem(container._items[x], container._rowHeight, container._rowGap);
502                    }
503
504                    for (let x = 0; x < container._items.length; x++) {
505                        container._items[x].style.gridRowEnd = "span " + container._items[x]._rowSpan;
506                    }
507                }
508            }
509
510			function resizeGridItem(item) {
511                item._rowSpan = Math.ceil((item.querySelector('.masonry-inner').getBoundingClientRect().height + container._rowGap)/(container._rowHeight + container._rowGap));
512            }
513
514			w.addEventListener("mq-resize", () => {
515				if (timeout == 0) {
516					container.classList.add("js-masonry-fallback");
517					masonryFunction(), 
518					timeout = 100, 
519					size = me.vanilla.layout._width;
520					return;
521				}
522				else {
523					size = me.vanilla.layout._width;
524
525					// Timeout: To stop browser from recalculating every single event,
526					// but instead do it in a reasonably fashion.
527					clearTimeout(setTimer);
528					setTimer = setTimeout(() => {
529						masonryFunction();
530					}, timeout);
531				}
532
533			}, {passive: true});
534		}
535
536		// Special, nothing to do with masonry in itself. Align items correctly in topic grid gallery
537
538		if (container.classList.contains('topic-grid')) {
539
540			container._items.forEach(item => {
541				me.f.visibilityClassToggler(item, false, true);
542			});
543
544			if (w.innerWidth >= 640 && container._items.length > 4) {
545
546				w.addEventListener("mq-resize", () => {
547					container._items.forEach(item => {
548						if (item.classList.contains("topic-grid-item-margin") && item.offsetLeft > (container.clientWidth / 2)) {
549							item.style.marginInline = "0 auto";
550						}
551						else if (item.classList.contains("topic-grid-item-margin")) {
552							item.style.marginInline = "auto 0";
553						}
554					});
555				}, {passive: true});
556			}
557		}
558	}
559
560	me.f.onepagerRoomlist = (container) => {
561
562		container = container.id ? container : me.vanilla.main.querySelector("." + container);
563		if (!container) return;
564
565		// Quicklinks will be handeled as a filter via JS, instead of scrolling to an area.
566
567		container._quicklinkBox = container.querySelector(":scope > .onepager-quicklinks");
568
569		if (container._quicklinkBox) {
570			container.append(container._quicklinkBox.cloneNode(true));
571		}
572
573		container._quicklinks = container.querySelectorAll(":scope > .onepager-quicklinks > a"),
574		container._cards = container.querySelectorAll(":scope > .onepager-list > *");
575
576		container._quicklinks.forEach((link, pos) => {
577			link.addEventListener("click", (e) => {
578				e.preventDefault();
579				if (!link.closest(".onepager-quicklinks").nextElementSibling) {
580					container.closest("section") && container.closest("section").previousElementSibling && container.closest("section").previousElementSibling.scrollIntoView();
581				}
582
583				container.classList.remove("filtered");
584				setTimeout(() => {
585					if (link.classList.contains('onepager-quicklink-all')) {
586						// Klick auf "Alle" Filter
587						container._cards.forEach(item => {
588							item.classList.remove("removed-by-filter");
589						});
590					}
591					else {
592						container._showItems = false;
593						container._cards.forEach(item => {
594							// Item in dem Kontext ist ein Onepager Item Zimmer oder die Headline der Zimmergruppe
595							if (item.nodeName === "H2" && !container._showItems) {
596								item.style.display = "none";
597								if (item.id === link.hash.substring(1, link.hash.length)) {
598									container._showItems = true;
599								}
600							}
601	
602							else if (item.nodeName === "H2") {
603								// Nächste Headline, also nach der erfolgreichen Filterung, reset.
604								container._showItems = false;
605							}
606	
607							if (container._showItems) {
608								item.classList.remove("removed-by-filter");
609							}
610							else {
611								item.classList.add("removed-by-filter");
612							}
613						});
614	
615						container._hiding = false;
616					}
617					container.classList.add("filtered");
618				}, 10);
619			});
620		});
621
622		container.querySelectorAll(".onepager-item-room-meta").forEach(meta => {
623			var newSubContainer = d.createElement("div");
624			newSubContainer.classList.add("onepager-custom-meta");
625
626			meta.parentElement.insertAdjacentElement("afterbegin", newSubContainer);
627
628			newSubContainer.append(meta);
629
630			var clone = meta.cloneNode(true);
631			newSubContainer.append(clone);
632
633			meta.querySelectorAll("li:nth-child(n + 3)").forEach(li => {
634				li.remove();
635			});
636
637			clone.querySelectorAll("li:nth-child(-n + 2)").forEach(li => {
638				li.remove();
639			});
640		});
641	}
642
643	me.f.widgetCloser = () => {
644		if (!me.opt.globalToggleWidget) return;
645
646		const widgetCloseLabel = d.createElement("label"),
647			  widgetCloseText  = me.opt.lang == 'de' ? "schließen" : "close",
648			  widgetHandleCTA  = me.vanilla.header.querySelector("label.widget-handle > .cta");
649
650		widgetCloseLabel.htmlFor = "global-toggle-widget",
651		widgetCloseLabel.id 	 = "js-widgetcloser";
652		widgetHandleCTA._contents = widgetHandleCTA.innerHTML;
653
654		const widgetCloserAppender = () => {
655			if (me.opt.globalToggleWidget.checked) {
656				me.vanilla.header.append(widgetCloseLabel);
657				widgetHandleCTA.textContent = widgetCloseText;
658			}
659			else {
660				widgetCloseLabel.remove();
661				widgetHandleCTA.textContent = "";
662				widgetHandleCTA.insertAdjacentHTML('beforeend', widgetHandleCTA._contents);
663			}
664		}
665
666		[me.opt.globalToggleMenu, me.opt.globalToggleWidget].forEach(toggle => {
667			toggle.addEventListener("change", widgetCloserAppender, {passive: true});
668		});
669	}
670
671	me.f.downloader = (btn) => {
672
673		if (btn.disabled) return;
674		
675		if (btn.dataset.type == 'all') {
676			var downloadText = 
677				me.opt.lang == "de" ? "Wirklich alle " + btn.dataset.files + " Bilder mit einer Gesamtgröße von " + btn.dataset.size + " MB herunterladen?" : 
678				"Do you really want to download all " + btn.dataset.files + " files? That will be " + btn.dataset.size + " MB.";
679
680			if (window.confirm(downloadText)) {
681				download_files(JSON.parse(btn.dataset.deliver));
682				btn.disabled = true;
683			}
684		}
685		else {
686			window.location.href = btn.dataset.deliver;
687			btn.disabled = true;
688		}
689	}
690
691    // Widget Flatpickr
692
693	me.customWidgetFp = (clientUrl, hotelId, objPrefix, objId, dateFormat) => {
694
695		me.opt.globalToggleWidget && me.opt.globalToggleWidget.addEventListener("change", () => {
696			me.customWidgetFpAction(clientUrl, hotelId, objPrefix, objId, dateFormat);
697		}, {passive: true, once: true});
698	}
699
700    me.customWidgetFpAction = function (clientUrl, hotelId, objPrefix, objId, dateFormat) {
701
702		var calendarInput = {
703			from: d.getElementById( objPrefix + '_date_from' ),
704			to: d.getElementById( objPrefix + '_date_to' )
705		}
706
707		var dateFromInit = calendarInput.from && calendarInput.from.dataset.date ? new Date(calendarInput.from.dataset.date) : new Date((new Date()).valueOf() + 24*60*60*1000),
708      	dateToInit = calendarInput.to && calendarInput.to.dataset.date ? new Date(calendarInput.to.dataset.date) : new Date((new Date()).valueOf() + 2*24*60*60*1000);
709		
710		// Set date value per default
711		if (calendarInput.from && calendarInput.from.dataset.date) {
712			calendarInput.from.value = me.date_convert(dateFromInit, "Date", 'iso');
713		}
714		if (calendarInput.to && calendarInput.to.dataset.date) {
715			calendarInput.to.value = me.date_convert(dateToInit, "Date", 'iso');
716		}
717
718        me.opt.avail = {},
719        me.opt.widgetSelectDates = {};
720		function getAvail(dateFrom, inst) {
721			dateFrom = dateFrom || new Date();
722			var requestFrom = me.date_convert(new Date(dateFrom.getFullYear(), dateFrom.getMonth(), 1), 'Date', 'ymd', 0, true);
723			var requestTo = me.date_convert(new Date(dateFrom.getFullYear(), dateFrom.getMonth() + 2, 1), 'Date', 'ymd');
724			if (me.opt.dev) {
725				console.info('Request: ' + requestFrom + ' -> ' + requestTo);
726			}
727			$.ajax({
728				dataType: 'json',
729				url: clientUrl + 'vsc.php',
730				data: {
731					return_json: true,
732					return_html: false,
733					step: 'calendar_from',
734					date_from: requestFrom,
735					date_to: requestTo,
736					cart_language: me.opt.lang,
737					c: {
738						id_hotel: hotelId
739					}
740				},
741				success: function (d) {
742					if (d.cart) {
743						$.map(d.cart.calendar.date_from.days, function (el, i) {
744							me.opt.avail[i] = el;
745						});
746						if (inst) {
747							inst.redraw();
748						}
749					}
750				}
751			});
752		}
753
754        function updateDate(inst, date) {
755          	date = date || inst.latestSelectedDateObj;
756          	if(!date){ return; }
757			var newDateDate = date.getDate();
758			if (newDateDate < 10) {
759				newDateDate = "0" + newDateDate;
760			}
761          	$(inst.element).closest('.widget-date').find('.widget-date-day').text(newDateDate);
762          	$(inst.element).closest('.widget-date').find('.widget-date-month').text(inst.l10n.months.shorthand[date.getMonth()]);
763
764            // If year of date_from and date_to is not the same, write both years
765            if (me.opt.widgetSelectDates.dateFrom && me.opt.widgetSelectDates.dateTo && (me.opt.widgetSelectDates.dateFrom.split("-")[0] !== me.opt.widgetSelectDates.dateTo.split("-")[0])) {
766                $(inst.element).closest('.widget-form-book').find('.widget-date-year').text(me.opt.widgetSelectDates.dateFrom.split("-")[0] + " - " + me.opt.widgetSelectDates.dateTo.split("-")[0]);
767            }
768            // Else if a year is present:
769            else if (me.opt.widgetSelectDates.dateFrom) {
770                $(inst.element).closest('.widget-form-book').find('.widget-date-year').text(me.opt.widgetSelectDates.dateFrom.split("-")[0]);
771            }
772        }
773
774        var fpConfig = {
775          	altInput: true,
776          	altFormat: dateFormat,
777          	locale: me.opt.lang,
778          	disableMobile: true,
779            static: true,
780          	minDate: 'today',
781          	onClose: function (selDates, dateStr, inst) {
782            	inst.altInput.blur();
783          	}
784        };
785
786        if ( objId == 'date_from' ) {
787          	$.extend( fpConfig, {
788            	onReady: function ( selDates, dateStr, inst ) {
789              		updateDate(inst, dateFromInit);
790              		inst.set( 'disable', [
791						function ( currentDay ) {
792							var availDay = me.opt.avail[ me.date_convert( currentDay, 'Date', 'ymd' ) ];
793							if ( availDay ) {
794								return !( availDay.type == 2 || availDay.type == 3 );
795							}
796							return true;
797						}
798              		]);
799              		getAvail( new Date(), inst );
800            	},
801            	onOpen: function ( selDates, dateStr, inst ) {
802              		inst.fpDateTo = d.getElementById( objPrefix + '_date_to' )._flatpickr;
803            	},
804            	onDayCreate: function ( dObj, dStr, inst, dayElem ) {
805              		var span;
806              		var dateIso = me.date_convert( dayElem.dateObj, 'Date', 'ymd' );
807              		if ( !me.opt.avail || !me.opt.avail[dateIso] ) {
808                		return;
809              		}
810              		if ( me.opt.avail[dateIso].type == 2 ) {
811                		span = d.createElement( 'span' );
812                		span.className = 'departure-only';
813                		dayElem.appendChild( span )
814              		}
815            	},
816            	onMonthChange: function ( selDates, dateStr, inst ) {
817              		if ( !me.opt.avail[ me.date_convert( new Date( inst.currentYear, inst.currentMonth + 1, 15 ), 'Date', 'ymd' ) ] ) {
818                		getAvail( new Date( inst.currentYear, inst.currentMonth, 1 ), inst );
819              		}
820            	},
821            	onYearChange: function ( selDates, dateStr, inst ) {
822					if ( !me.opt.avail[ me.date_convert( new Date( inst.currentYear, inst.currentMonth + 1, 15 ), 'Date', 'ymd' ) ] ) {
823                		getAvail( new Date( inst.currentYear, inst.currentMonth, 1 ), inst );
824              		}
825            	},
826            	onChange: function ( selDates, dateStr, inst ) {
827              		if ( inst.latestSelectedDateObj ) {
828                        me.opt.widgetSelectDates.dateFrom = dateStr;
829                		updateDate(inst);
830                		inst.fpDateTo.clear();
831                		inst.fpDateTo.set( 'minDate', me.date_convert( inst.latestSelectedDateObj, 'Date', 'Date', 1 ) );
832                		inst.fpDateTo.set( 'maxDate', me.date_convert( inst.latestSelectedDateObj, 'Date', 'Date', 28 ) );
833                		inst.fpDateTo.redraw();
834                		inst.fpDateTo.open();
835                		$( 'input.rdfrom1', $( inst.element ).closest( 'form' ) ).val( me.date_convert( inst.latestSelectedDateObj, 'Date', 'dmy' ) );
836                		$('.widget-date-from').addClass('changed');
837              		}
838            	}
839          	});
840        }
841        else if (objId === 'date_to') {
842          	$.extend(fpConfig, {
843            	onReady: function (selDates, dateStr, inst) {
844              		updateDate(inst, dateToInit);
845              		inst.set('disable', [
846                		function (currentDay) {
847                  			var availDay = me.opt.avail[me.date_convert(currentDay, 'Date', 'ymd')];
848                  			if (availDay) {
849                    			return availDay.type === -1;
850                  			}
851                  			return true;
852                		}
853              		]);
854              		inst.fpDateFrom = d.getElementById(objPrefix + '_date_from')._flatpickr;
855            	},
856            	onDayCreate: function (dObj, dStr, inst, dayElem) {
857              		if (!inst.fpDateFrom || !inst.fpDateFrom.latestSelectedDateObj) {
858                		return;
859              		}
860              		var $dayElem = $(dayElem);
861              		var fromDate = me.date_convert(inst.fpDateFrom.latestSelectedDateObj, 'Date', 'ymd');
862              		var toDate = inst.latestSelectedDateObj ? me.date_convert(inst.latestSelectedDateObj, 'Date', 'ymd') : false;
863              		var currentDate = me.date_convert(dayElem.dateObj, 'Date', 'ymd');
864              		$dayElem.toggleClass('arrival-day', currentDate === fromDate)
865						.toggleClass('following-day', currentDate > fromDate)
866						.toggleClass('between-day', currentDate > fromDate && currentDate < toDate)
867						.filter('.following-day:not(.disabled)')
868						.hover(
869							function () {
870								$dayElem.prevAll('.following-day').addBack().addClass('following-day-hover');
871							},
872							function () {
873								$dayElem.prevAll('.following-day').addBack().removeClass('following-day-hover');
874							}
875						);
876            	},
877            	onChange: function (selDates, dateStr, inst) {
878              		if (inst.latestSelectedDateObj) {
879                        me.opt.widgetSelectDates.dateTo = dateStr;
880                		updateDate(inst);
881                		$('input.rdto1', $(inst.element).closest('form')).val(me.date_convert(inst.latestSelectedDateObj, 'Date', 'dmy'));
882                		$('.widget-date-to').addClass('changed');
883              		};
884            	}
885          	});
886        }
887        $('#' + objPrefix + '_' + objId).flatpickr(fpConfig);
888	};
889
890	me.loadingClasses = function() {
891		if (me.opt.touch) {
892			me.opt.html.removeClass('no-touchevents').addClass('touchevents');
893		}
894		if (me.opt.intersectionobserver) {
895			me.opt.html.addClass('intersectionobserver');
896		}
897	}
898
899	me.globalToggles = me.vanilla.site.querySelectorAll(":scope > .global-toggle");
900
901	me.globalToggles.forEach(gt => {
902		gt.addEventListener("change", () => {
903			var isChecked = gt.checked;
904			me.globalToggles.forEach(gt2 => {
905				gt2.checked = false;
906			});
907			gt.checked = isChecked;
908		}, {passive: true});
909	});
910
911	me.opt.bgChangeElements.forEach(section => {
912		me.f.visibilityClassToggler(section, "darken-page", false, {rootMargin: "-50% -0% -50% -0%"});
913	});
914
915	me.vanilla.main.querySelectorAll(":scope > .grp-single-87[data-attr-layout^='bild-text-box']").forEach(imagetextbox => {
916		me.f.visibilityClassToggler(imagetextbox);
917	});
918
919	me.vanilla.main.querySelectorAll("video:not(.nst21-video-autoplay)").forEach(video => {
920			
921		if (video.dataset.poster) {
922			video.poster = video.dataset.poster;
923		}
924		else if (navigator.userAgent.includes("Safari")) {
925			video.currentTime = 0.001;
926			video.preload = "metadata";
927			video.querySelectorAll("source").forEach(source => {
928				if (source.type.includes("video")) {
929					source.src = source .src + "#t=0.001";
930				}
931			});
932
933			var clone = video.cloneNode(true);
934
935			video.insertAdjacentHTML("afterend", clone.outerHTML);
936			video.remove();
937		}
938		else {
939			video.style.backgroundColor = "#000000";
940		}
941	});
942
943	me.onloadFunctions = () => {
944		// Functions that can be delayed or should occur _after_ load event of the whole page
945
946		me.vanilla.site.classList.remove("no-animations"),
947		me.vanilla.site.classList.add("animations-allowed");
948
949		me.f.resizeObserver();
950		me.f.mailificator();
951		me.f.mobileNumberficator();
952		me.f.widgetCloser();
953	}
954
955	nst2021.f.smallSliderSlick = (el) => {
956		// Entgegen des Namens wurde doch kein Slick Slider verwendet. Custom JS.
957		el = el.id ? el : me.vanilla.main.querySelector("#" + el);
958
959		if (!el) return console.log("No small slider gallery found. You need to give it an unique id");
960
961		// Setup:
962		el._itemCount = el.childNodes.length,
963		el._itemPos = 0,
964		el._arrowPrev = el.closest(".small-itemslider-mobilescroll").nextElementSibling.querySelector(".slide-arrow-left"),
965		el._arrowNext = el.closest(".small-itemslider-mobilescroll").nextElementSibling.querySelector(".slide-arrow-right");
966
967		if (el._itemCount > 1) {
968
969			let slideFunction = (pos) => {
970
971				// preload images as soon as user clicks an arrow
972				if (!el._slideActive) {
973					el.querySelectorAll("img").forEach(img => {
974						img.loading = "eager";
975					})
976					el._slideActive = true;
977				}
978
979				let newPos = el._itemPos + pos,
980				skip = window.innerWidth > 960 ? 2 : 1;
981
982				if (newPos < 0 ) return; // Nicht nach links wenn Position Start wäre
983				if (newPos > el._itemCount - skip) return;
984
985				el.childNodes.forEach(item => {
986					item.style.transform = "translateX(calc(-" + (100 * newPos) + "% - " + (40 * newPos) + "px))";
987				});
988
989				el._itemPos = newPos;
990
991				// disable buttons if first or last item
992				if (el._itemPos == 0) {
993					el._arrowPrev.setAttribute("disabled", "disabled");
994				}
995				else {
996					el._arrowPrev.removeAttribute("disabled", "disabled");
997				}
998				if (el._itemPos + skip == el._itemCount) {
999					el._arrowNext.setAttribute("disabled", "disabled");
1000				}
1001				else {
1002					el._arrowNext.removeAttribute("disabled", "disabled");
1003				}
1004			}
1005
1006			el._arrowNext.addEventListener("click", () => {
1007				slideFunction(1);
1008			}, {passive: true});
1009
1010			el._arrowPrev.addEventListener("click", () => {
1011				slideFunction(-1);
1012			}, {passive: true});
1013
1014			if (!w.matchMedia('(prefers-reduced-motion: reduce)').matches) {
1015				el.childNodes.forEach(slide => {
1016					me.f.visibilityClassToggler(slide, false, true, {
1017						rootMargin: "-20% 0% -20% 0%"
1018					});
1019				});
1020			}
1021		}
1022		else {
1023			el._arrowPrev.remove(),
1024			el._arrowNext.remove();
1025		}
1026	}
1027
1028	me.domReadyFunctions = () => {
1029		
1030		let trackingConsentMinimize = d.body.querySelector(":scope > #condeon-consentmanager > button.tracking-consent-minimize");
1031
1032		if (trackingConsentMinimize) {
1033			trackingConsentMinimize.ariaLabel = me.opt.lang == "de" ? "Datenschutzeinstellungen minimieren" : "Minimize privacy references";
1034		}
1035
1036		if (!me.opt.touch) {
1037			me.opt.mainMenuHandles = me.vanilla.header.querySelectorAll("label.main-menu-handle");
1038
1039			me.opt.mainMenuHandleMouseChecker = (el) => {
1040				el._toggle = el.target.closest(".main-menu-item").querySelector(".main-menu-toggle");
1041
1042				if (el._toggle) {
1043					el._toggle.checked = true;
1044				}
1045			}
1046
1047			me.opt.mainMenuHandles.forEach(handle => {
1048				handle.addEventListener("mouseenter", me.opt.mainMenuHandleMouseChecker, {passive: true});
1049			})
1050		}
1051	}
1052
1053	me.trackingEnabled = () => {
1054		setTimeout(() => {
1055			// NOTE: The value 67 in the next line is for Google Analytics 4 tracking code without signals, 70 with signals
1056			if (document.cookie.match(/(?:^| )tracking_consent=c(?:[0-9]+,)*(67)(?:,|;|$)/) || document.cookie.match(/(?:^| )tracking_consent=c(?:[0-9]+,)*(70)(?:,|;|$)/)) {
1057				let newCetScript = document.createElement("script");
1058				newCetScript.src = "/(cms)/module/static/default/content/libs/js/analytics-click-events/cet.min.js";
1059				d.body.append(newCetScript);
1060			}
1061		}, 10);
1062	}
1063
1064	me.track = {
1065		
1066		quicklinks: {
1067			name:	"Header Quicklinks Navigation",
1068			el: 	$(".nav-quicklinks a", me.opt.header)
1069		},
1070		logo: {
1071			name: 	"Header Logo",
1072			subtype: "logo",
1073			el:		$(".logo a", me.opt.header)
1074		},
1075		familotellogoheader: {
1076			name:	"Familotel Logo im Header",
1077			el:		$("a.familotel-label", me.opt.header)
1078		},
1079		headergalCta: {
1080			name:	"Header Galerie CTA",
1081			el:		$(".custom-gallery-grid-1 a", me.opt.header),
1082		},
1083		poi: {
1084            name: 	"Header POI Link",
1085            el: 	$('a.nst21-poi', me.opt.header)
1086        },
1087		topicGridItem: {
1088			name: 	"Normale Galerie, Klick auf Bild",
1089			el:		$("a.topic-grid-item", me.opt.main)
1090		},
1091		imageTextBoxCTA: {
1092			name:	"Bild Text Box CTA",
1093			el:		$(".image-text-box a[class*='cta']", me.opt.main)
1094		},
1095		cstCardDetailLink: {
1096            name: "CST Card Detail Link",
1097            el: $('.grp-mem-57 a.small-itemslider-link', me.opt.main),
1098            labeladd: 'h4',
1099            labelin: '.small-itemslider-item',
1100            labelAsCategory: true
1101        },
1102		bildergalerieSlider: {
1103            name: "Bildergalerie Slider",
1104            el: $('.grp-mem-54 a.small-itemslider-link', me.opt.main)
1105        },
1106		pseudoMaillink: {
1107            el: $('a[data-mail]', me.opt.site),
1108            name: "Email link"
1109        },
1110		pseudoTellink: {
1111            el: $('a[data-mobile]', me.opt.site),
1112            name: "Phone link"
1113        },
1114		roomFilter: {
1115			name: "Onepager Zimmer Filter/Quicklink",
1116            el: $('a.onepager-quicklink', me.opt.main)
1117		},
1118		roomBook: {
1119			name: "Onepager Zimmer buchen und anfragen",
1120            el: $('.onepager-list form.cst-bookform', me.opt.main),
1121            labeladd: '.onepager-item-headline',
1122            labelin: '.onepager-item',
1123            labelAsCategory: true
1124		},
1125		cstDetailAction: {
1126            name: "CST Detail Action Button",
1127            el: $('.gallery-actions-buttons form, .cst-detail-actions form', me.opt.site),
1128            subtype: $('.cst-detail-actions').length >= 2 ? 'packagevariant' : '',
1129            labeladd: '.gallery-headlines h1',
1130            labelin: '.site'
1131        },
1132	};
1133	
1134
1135	w.addEventListener("DOMContentLoaded", me.domReadyFunctions, {passive: true});
1136	w.addEventListener("load", me.onloadFunctions, {passive: true});
1137	w.addEventListener("scroll", me.opt.scrollBarVisibility, {passive: true});
1138	d.addEventListener('CondeonTrackingEnabled', me.trackingEnabledFunctions, {passive: true});
1139
1140
1141
1142	me.opt.globalToggleMenu.addEventListener("change", () => {
1143		// Give Safari some time to calculate scrollbar width
1144		me.opt.globalToggleMenu.checked = false;
1145		d.body.style.setProperty("--scrollbar-width", w.innerWidth - d.body.clientWidth + "px");
1146		me.opt.globalToggleMenu.checked = true;
1147		
1148	}, {passive: true, once: true});
1149
1150	// check if headings have duplicate IDs and/or anchors have duplicate IDs or same IDs
1151	// NOTE Duplicate ID checker only for headings and anchors
1152	me.handleDuplicateHeadingIds = function() {
1153
1154		const headings = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
1155		const anchors = document.querySelectorAll("[data-attr-sprungmarke]");
1156		const idCounts = {};
1157
1158		// Handle ID duplicates on anchors/headings
1159		const handleDuplicateId = (element) => {
1160
1161			if (!element.id || element.id.trim() === "") {
1162				element.removeAttribute("id");
1163				return;
1164			}
1165
1166			idCounts[element.id] = (idCounts[element.id] || 0) + 1;
1167
1168			if (idCounts[element.id] > 1) {
1169				console.log(`Element with ID "${element.id}" has been processed, count: ${idCounts[element.id]}`);
1170			}
1171
1172			if (idCounts[element.id] > 1) {
1173				element.id = `${element.id}_${idCounts[element.id] - 1}`;
1174			}
1175		};
1176
1177		anchors.forEach(handleDuplicateId);
1178		headings.forEach(handleDuplicateId);
1179
1180		// Update/test anchors to match headings
1181		anchors.forEach(anchor => {
1182			if (!anchor.id) return;
1183		
1184			headings.forEach(heading => {
1185
1186				if (!heading.id) return;
1187				if (heading.id === anchor.id) {
1188					idCounts[heading.id]++;
1189					heading.id = `${heading.id}_${idCounts[heading.id]}`;
1190				}
1191			});
1192		});
1193	};
1194
1195	const navMenuCheckbox = document.getElementById("global-toggle-menu");
1196	const navContainer = document.querySelector(".nav-level-0");
1197
1198	// Uncheck menu checkbox when Escape key is pressed
1199	if (navMenuCheckbox) {
1200		document.addEventListener("keydown", function (e) {
1201			if (e.key === "Escape" || e.code === 27) {
1202				
1203				// If the nav menu is open, close it
1204				if (navMenuCheckbox.checked) {
1205					navMenuCheckbox.checked = false;
1206				}
1207			}
1208		});
1209	}
1210
1211	// Remove hrefs from flex-prev and flex-next links
1212	window.addEventListener('load', function() {
1213		document.querySelectorAll('a.flex-prev, a.flex-next, a.slick-prev, a.slick-next').forEach(link => {
1214			if (!link.getAttribute('href') || link.getAttribute('href') === '' || link.getAttribute('href') === '#') {
1215				link.removeAttribute('href');
1216			}
1217		});
1218	});
1219
1220
1221	me.handleDuplicateHeadingIds();
1222
1223})( window.jQuery, window.nst2021, window, document );

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.