PageSourceSearch

https://valara.at/wp-content/themes/twentytwenty-child/view.js?ver=1770290702

js valara.at collected 2026-10-09 21:57:52 UTC 16,598 bytes, 680 lines download raw bytes

1/**
2 * ==========================================
3 * general scripts for majority of pages
4 */
5
6document.addEventListener("DOMContentLoaded", function () {
7	// main menu toggle
8	menu_toggle();
9
10	// submenus toggle
11	subm_toggle();
12
13	// submenus toggle
14	subm_toggle_mob();
15
16	// sticky header
17	sticky_head();
18
19	// init swiper sliders
20	init_sliders();
21
22	// lazy load videos
23	init_lazy_videos();
24
25	// init tabs
26	toggle_tabs();
27
28	// init accordion
29	accordion();
30
31	// detect hero-section page
32	check_hero();
33
34	// flickr init (jQuery form tricks)
35	flickr_init();
36
37	// custom form tweaks
38	cf7_tweaks();
39});
40
41/**
42 * main menu toggle
43 */
44function menu_toggle() {
45	const body = document.querySelector("body");
46
47	const toggle = document.querySelector(".menu-toggle");
48
49	toggle.addEventListener("click", function () {
50		const isOpen = body.classList.toggle("menu-open");
51
52		toggle.setAttribute("aria-expanded", isOpen);
53
54		// acivate the 1st menu item
55		const menuItems = document.querySelectorAll(".main-menu .menu-item");
56
57		if (menuItems.length > 0) {
58			const bgImg = document.querySelector(".main-menu .bg-img");
59			const firstItem = menuItems[0];
60
61			const bg = menuItems[0]?.getAttribute("data-bg");
62			bgImg.style.background = bg || "none";
63
64			// 🔑 Add the class that contains the hover styles
65			firstItem.classList.add("is-active");
66		}
67	});
68}
69
70/**
71 * submenus hover + bgImage
72 */
73function subm_toggle2() {
74	/* 	const langClass = Array.from(document.body.classList).find((c) =>
75		c.startsWith("language-")
76	);
77	const lang = langClass ? langClass.slice(-3) : null; */
78
79	const menu = document.querySelector('.main-menu ul[id^="menu-"]');
80
81	const wrapper = document.getElementById("sub-wrapper");
82	const bgImg = document.querySelector(".main-menu .bg-img");
83
84	if (!menu || !wrapper || !bgImg) return;
85
86	// Delegate hover events to menu items
87	menu.querySelectorAll(".menu-item").forEach((menuItem) => {
88		const link = menuItem.querySelector("a");
89		const submenu = menuItem.querySelector(".sub-menu");
90
91		menuItem.addEventListener("mouseenter", () => {
92			const bg = menuItem?.getAttribute("data-bg");
93			bgImg.style.background = bg || "none";
94
95			// Clone submenu into wrapper
96			wrapper.innerHTML = "";
97			if (submenu) {
98				const clone = submenu.cloneNode(true);
99				wrapper.append(clone);
100
101				menuItem.classList.add("submenu-open");
102			}
103		});
104
105		// When leaving the menu item
106		menuItem.addEventListener("mouseleave", (e) => {
107			// Only clear if mouse leaves the whole menu area
108			if (!menu.contains(e.relatedTarget)) {
109				bgImg.style.background = "none";
110				wrapper.innerHTML = "";
111			}
112
113			menuItem.classList.remove("submenu-open");
114		});
115	});
116}
117
118/**
119 * submenus hover + bgImage
120 */
121function subm_toggle() {
122	/* 	const langClass = Array.from(document.body.classList).find((c) =>
123		c.startsWith("language-")
124	);
125	const lang = langClass ? langClass.slice(-3) : null; */
126
127	const menu = document.querySelector('.main-menu ul[id^="menu-"]');
128	if (!menu) return;
129
130	const bgImg = document.querySelector(".main-menu .bg-img");
131	const menuItems = document.querySelectorAll(".main-menu .menu-item");
132
133	if (!menu || !bgImg) return;
134
135	// Delegate hover events to menu items
136	menuItems.forEach((menuItem) => {
137		const link = menuItem.querySelector("a");
138		const submenu = menuItem.querySelector(".sub-menu");
139
140		if (!submenu) return;
141
142		menuItem.addEventListener("mouseenter", () => {
143			// deactivate all submenu items
144			menuItems.forEach((i) => i.classList.remove("is-active"));
145
146			menuItem.classList.add("is-active");
147
148			const mediaType = menuItem?.getAttribute("data-media-type");
149			const videoUrl = menuItem?.getAttribute("data-video-url");
150			const bg = menuItem?.getAttribute("data-bg");
151
152			// Clear existing video
153			const existingVideo = bgImg.querySelector("video");
154			if (existingVideo) {
155				existingVideo.remove();
156			}
157
158			if (mediaType === "video" && videoUrl) {
159				// Hide background image
160				bgImg.style.background = "none";
161				// Add video element
162				const video = document.createElement("video");
163				video.autoplay = true;
164				video.muted = true;
165				video.loop = true;
166				video.playsInline = true;
167				video.className = "menu-bg-video";
168				const source = document.createElement("source");
169				source.src = videoUrl;
170				source.type = "video/mp4";
171				video.appendChild(source);
172				bgImg.appendChild(video);
173			} else {
174				// Use background image
175				bgImg.style.background = bg || "none";
176			}
177
178			menuItem.classList.add("submenu-open");
179		});
180
181		// When leaving the menu item
182		menuItem.addEventListener("mouseleave", (e) => {
183			// Only clear if mouse leaves the whole menu area
184			if (!menuItem.contains(e.relatedTarget)) {
185				menuItem.classList.remove("is-active");
186				menuItem.classList.remove("submenu-open");
187
188				if (!menu.contains(e.relatedTarget)) {
189					bgImg.style.background = "none";
190					const existingVideo = bgImg.querySelector("video");
191					if (existingVideo) {
192						existingVideo.remove();
193					}
194					wrapper.innerHTML = "";
195				}
196			}
197		});
198	});
199}
200
201/**
202 * submenus open on mob
203 */
204function subm_toggle_mob() {
205	const menu = document.querySelector('.main-menu-mob ul[id^="menu-"]');
206
207	if (!menu) return;
208
209	const menuItems = menu.querySelectorAll(".menu-item");
210
211	// delegate click events on menu items
212	menuItems.forEach((menuItem) => {
213		const submenu = menuItem.querySelector(".sub-menu");
214
215		if (!submenu) return;
216
217		menuItem.addEventListener("click", (e) => {
218			const link = e.target.closest("a");
219
220			// If click is inside submenu (child link), allow navigation
221			if (link && link.closest(".sub-menu")) {
222				return;
223			}
224
225			e.preventDefault();
226
227			menuItems.forEach((i) => i.classList.remove("is-active", "submenu-open"));
228
229			menuItem.classList.add("is-active", "submenu-open");
230		});
231	});
232}
233
234/**
235 * make header sticky when scrolling down
236 */
237function sticky_head() {
238	const header = document.getElementById("site-header");
239
240	if (!header) {
241		console.error("Error: Element with ID 'site-header' not found.");
242		return; // Exit the function if the element is missing
243	}
244
245	const stickyOffset = header.offsetTop + 200;
246
247	window.onscroll = function () {
248		if (window.scrollY >= stickyOffset) {
249			header.classList.add("sticky");
250		} else {
251			header.classList.remove("sticky");
252		}
253	};
254}
255
256/**
257 * init swiper sliders - per-position
258 */
259function init_sliders() {
260	// hero
261	let args = {
262		loop: true,
263		slidesPerView: 1,
264		spaceBetween: 0,
265
266		speed: 700,
267		preloadImages: false, // prevent full preload
268		lazy: {
269			loadPrevNext: true, // optional: preload next/prev slides for smooth transition
270		},
271		// Progress bar
272		pagination: {
273			el: ".swiper-pagination-progressbar",
274			type: "progressbar",
275		},
276		autoplay: {
277			delay: 5000,
278			disableOnInteraction: false, // optional but recommended
279		},
280	};
281
282	set_slider("hero", args);
283
284	// gallery
285	args = {
286		loop: false,
287		centeredSlidesBounds: true,
288		centeredSlides: false,
289		watchSlidesProgress: true,
290		slidesPerView: "auto",
291		preloadImages: false, // prevent full preload
292		lazy: {
293			loadPrevNext: true, // optional: preload next/prev slides for smooth transition
294		},
295		breakpoints: {
296			320: {
297				slidesPerView: 1,
298			},
299			960: {
300				slidesPerView: 3,
301			},
302			1440: {
303				slidesPerView: "auto",
304			},
305		},
306	};
307	set_slider("gallery", args);
308
309	// packages
310	args = {
311		preloadImages: false, // prevent full preload
312		lazy: {
313			loadPrevNext: true, // optional: preload next/prev slides for smooth transition
314		},
315		breakpoints: {
316			320: {
317				slidesPerView: 1,
318			},
319			960: {
320				slidesPerView: 3,
321			},
322		},
323	};
324	set_slider("packages", args);
325
326	//  teaser2
327	args = {
328		centeredSlidesBounds: true,
329		centeredSlides: false,
330		watchSlidesProgress: true,
331		spaceBetween: 34,
332		preloadImages: false, // prevent full preload
333		lazy: {
334			loadPrevNext: true, // optional: preload next/prev slides for smooth transition
335		},
336		breakpoints: {
337			320: {
338				spaceBetween: 34,
339				slidesPerView: "auto",
340			},
341			960: {
342				spaceBetween: 50,
343				slidesPerView: 2,
344			},
345		},
346	};
347	set_slider("teaser2", args);
348
349	// teaser3
350	args = {
351		enabled: true,
352		preloadImages: false, // prevent full preload
353		lazy: {
354			loadPrevNext: true, // optional: preload next/prev slides for smooth transition
355		},
356		breakpoints: {
357			320: {
358				slidesPerView: 1,
359			},
360			960: {
361				slidesPerView: 3,
362			},
363		},
364	};
365	set_slider("teaser3", args);
366}
367
368/**
369 * Initialize lazy loading for videos
370 */
371function init_lazy_videos() {
372	// Check if Intersection Observer is supported
373	if (!('IntersectionObserver' in window)) {
374		// Fallback: load all videos immediately
375		document.querySelectorAll('video[data-lazy-video="true"]').forEach((video) => {
376			video.setAttribute('preload', 'auto');
377		});
378		return;
379	}
380
381	// Find all videos with lazy loading enabled
382	const lazyVideos = document.querySelectorAll('video[data-lazy-video="true"]');
383
384	if (lazyVideos.length === 0) return;
385
386	// Create Intersection Observer with rootMargin for earlier loading
387	const videoObserver = new IntersectionObserver(
388		(entries) => {
389			entries.forEach((entry) => {
390				if (entry.isIntersecting) {
391					const video = entry.target;
392					// Change preload to auto to start loading
393					video.setAttribute('preload', 'auto');
394					// Load the video
395					video.load();
396					// Stop observing this video
397					videoObserver.unobserve(video);
398				}
399			});
400		},
401		{
402			rootMargin: '50px', // Start loading 50px before video enters viewport
403			threshold: 0.01,
404		}
405	);
406
407	// Observe all lazy videos
408	lazyVideos.forEach((video) => {
409		videoObserver.observe(video);
410	});
411}
412
413/**
414 * Load videos in Swiper slider when slide becomes active
415 */
416function load_swiper_videos(swiperInstance) {
417	if (!swiperInstance) return;
418
419	// Load video in current slide
420	const currentSlide = swiperInstance.slides[swiperInstance.activeIndex];
421	if (currentSlide) {
422		const video = currentSlide.querySelector('video[data-lazy-video="true"]');
423		if (video && video.getAttribute('preload') === 'none') {
424			video.setAttribute('preload', 'auto');
425			video.load();
426		}
427	}
428
429	// Preload videos in adjacent slides (next/prev)
430	const nextIndex = swiperInstance.activeIndex + 1;
431	const prevIndex = swiperInstance.activeIndex - 1;
432
433	[nextIndex, prevIndex].forEach((index) => {
434		if (swiperInstance.slides[index]) {
435			const video = swiperInstance.slides[index].querySelector('video[data-lazy-video="true"]');
436			if (video && video.getAttribute('preload') === 'none') {
437				video.setAttribute('preload', 'metadata');
438				video.load();
439			}
440		}
441	});
442}
443
444// set any slider
445function set_slider(pos, args = {}) {
446	const wrapper = `swiper-container-${pos}`;
447
448	document.querySelectorAll(`[class*="${wrapper}"]`).forEach((el) => {
449		// slider unique id
450		const id = el.dataset.id || "";
451
452		const argset = get_special(el, structuredClone(args));
453
454		const defaults = {
455			loop: true,
456			slidesPerView: 2,
457			spaceBetween: 50,
458			autoplay: false,
459			speed: 300,
460			navigation: {
461				nextEl: `.swiper-${pos}-next-${id}`,
462				prevEl: `.swiper-${pos}-prev-${id}`,
463			},
464		};
465
466		const options = {
467			...defaults,
468			...argset,
469		};
470
471		const swiperInstance = new Swiper(el, options);
472
473		// Load videos when slide changes
474		swiperInstance.on('slideChange', () => {
475			load_swiper_videos(swiperInstance);
476		});
477
478		// Load videos in initial slide
479		load_swiper_videos(swiperInstance);
480	});
481}
482
483// get data attr for special settings
484function get_special(el, args) {
485	// disable on mobile
486	const disableOnMob = el.dataset.disable === "1"; // from ACF
487
488	args.breakpoints ||= {};
489
490	if (disableOnMob) {
491		args.breakpoints[0] = { enabled: true };
492		args.breakpoints[320] = {
493			...args.breakpoints[320],
494			enabled: true,
495		};
496		args.breakpoints[960] = {
497			...args.breakpoints[960],
498			enabled: true,
499		};
500	}
501
502	return args;
503}
504
505/**
506 * tabs (toggle tabs content)
507 */
508function toggle_tabs() {
509	document.querySelectorAll(".wrapper.tabs").forEach((tabset) => {
510		const headers = tabset.querySelectorAll("[data-key].tabs-header");
511
512		headers.forEach((header) => {
513			header.addEventListener("click", () => {
514				const key = header.dataset.key;
515
516				// get all headers and all content
517				const allHeaders = tabset.querySelectorAll(".tabs-header");
518				const allContent = tabset.querySelectorAll(".tabs-content");
519
520				// find the matching content
521				const content = tabset.querySelector(
522					`[data-key="${key}"].tabs-content`
523				);
524
525				if (!content) return;
526
527				// deactivate all headers' buttons
528				allHeaders.forEach((head) => {
529					head.classList.remove("active");
530				});
531
532				allContent.forEach((cnt) => {
533					cnt.classList.remove("active");
534				});
535
536				header.classList.add("active");
537				content.classList.add("active");
538			});
539		});
540	});
541}
542
543/**
544 * accordion (classic)
545 * - .accordion .accordion-item {.accordion-header .accordion-content}
546 */
547function accordion() {
548	const accord = document.querySelector(
549		".accordion > .accordion-item:first-child"
550	);
551
552	if (!accord) return;
553
554	accord.classList.add("active");
555
556	document.querySelectorAll(".accordion-item").forEach((item) => {
557		item.addEventListener("click", () => {
558			const isOpen = item.classList.contains("active");
559
560			// Close all
561			document
562				.querySelectorAll(".accordion-item")
563				.forEach((c) => c.classList.remove("active"));
564
565			// Open clicked one
566			if (!isOpen) item.classList.add("active");
567		});
568	});
569}
570
571/**
572 * detect hero-section page
573 */
574function check_hero() {
575	const has_hero = document.querySelector(".sect-hero");
576
577	if (!has_hero) return;
578
579	document.body.classList.add("has-hero");
580}
581
582/**
583 * flickr init (jQuery form tricks)
584 */
585function flickr_init() {
586	const el = document.getElementById("travel_period");
587	if (!el) return;
588
589	flatpickr(el, {
590		mode: "range",
591		dateFormat: "d.m.Y",
592		minDate: "today", // optional: prevent past dates
593	});
594}
595
596/**
597 * custom form tweaks
598 */
599function cf7_tweaks() {
600
601	const repeater = document.querySelector('.wpcf7-form label[id^="repeater_"]');
602
603	if (!repeater) {
604		// no conditional repeater field set on page
605		return;
606	}
607
608	const rep_id = repeater.id;
609
610	const rep_fields = document.querySelectorAll(`#${rep_id}_fields label`);
611
612	rep_fields.forEach((field, i) => {
613		let el = field.querySelector(`#kid_${i + 1}`);
614
615		// clear value
616		el.value = "";
617
618		// remove required class and attributes
619		el.classList.remove("wpcf7-validates-as-required");
620		el.removeAttribute("aria-required");
621
622		// remove previous invalid state
623		el.classList.remove("wpcf7-not-valid");
624		el.removeAttribute("aria-invalid");
625
626		// remove tip span if exists
627		const tip = field.querySelector(".wpcf7-not-valid-tip");
628		if (tip) tip.remove();
629
630		/* 		// **reset CF7 internal validation**
631		if (typeof wpcf7 !== "undefined") {
632			wpcf7.clearResponse(el.closest(".wpcf7-form"));
633		} */
634	});
635
636	repeater.addEventListener("change", (e) => {
637		const slct = parseInt(e.target.value, 10);
638
639		rep_fields.forEach((field, i) => {
640			let el = field.querySelector(`#kid_${i + 1}`);
641			// show all repeating fields
642			field.classList.add("hideMe");
643			el.value = "";
644			el.classList.add("wpcf7-validates-as-required");
645			el.setAttribute("aria-required", "true");
646			if (i < slct) {
647				// hide after the selected number
648				field.classList.remove("hideMe");
649			} else {
650				el.value = "";
651				el.classList.remove("wpcf7-validates-as-required");
652				el.removeAttribute("aria-required");
653				el.classList.remove("wpcf7-not-valid");
654				el.removeAttribute("aria-invalid");
655			}
656		});
657	});
658}
659
660function cf7_tweaks2() {
661	const numKids = document.getElementById("number_kids");
662	const kid1 = document.getElementById("lab_kid_1");
663	const kid2 = document.getElementById("lab_kid_2");
664
665	if (numKids > 0) {
666		numKids.addEventListener("change", () => {
667			const index = numKids.selectedIndex;
668
669			kid1.style.visibility = "hidden";
670			kid2.style.visibility = "hidden";
671
672			if (index === 1) {
673				kid1.style.visibility = "visible";
674			} else if (index === 2) {
675				kid1.style.visibility = "visible";
676				kid2.style.visibility = "visible";
677			}
678		});
679	}
680}

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.