import './bootstrap';

import Alpine from 'alpinejs';
import collapse from '@alpinejs/collapse';
import Swiper from 'swiper';
import { Autoplay, Pagination, Navigation } from 'swiper/modules';

window.Alpine = Alpine;

Alpine.plugin(collapse);
Alpine.start();

function initNewsSwiper() {
	const element = document.querySelector('[data-news-swiper]');
	if (!element) return;

	// Avoid double init (e.g., hot reload)
	if (element.dataset.swiperInitialized === 'true') return;
	element.dataset.swiperInitialized = 'true';

	new Swiper(element, {
		modules: [Pagination, Autoplay],
		loop: true,
		slidesPerView: 1,
		speed: 650,
		autoplay: {
			delay: 4500,
			disableOnInteraction: false,
		},
		pagination: {
			el: element.querySelector('.swiper-pagination'),
			clickable: true,
		},
	});
}

function initFacilitiesSwiper() {
	const element = document.querySelector('[data-facilities-swiper]');
	if (!element) return;

	// Avoid double init (e.g., hot reload)
	if (element.dataset.swiperInitialized === 'true') return;
	element.dataset.swiperInitialized = 'true';

	new Swiper(element, {
		modules: [Navigation],
		slidesPerView: 1,
		spaceBetween: 16,
		speed: 650,
		grabCursor: true,
		watchOverflow: true,
		navigation: {
			nextEl: '.facilities-swiper-button-next',
			prevEl: '.facilities-swiper-button-prev',
		},
		breakpoints: {
			640: {
				slidesPerView: 2,
				spaceBetween: 16,
			},
			1024: {
				slidesPerView: 2,
				spaceBetween: 20,
			},
		},
	});
}

function initTrainersSwiper() {
	const element = document.querySelector('[data-trainers-swiper]');
	if (!element) return;

	// Avoid double init (e.g., hot reload)
	if (element.dataset.swiperInitialized === 'true') return;
	element.dataset.swiperInitialized = 'true';

	new Swiper(element, {
		modules: [Navigation],
		slidesPerView: 1,
		spaceBetween: 16,
		speed: 650,
		grabCursor: true,
		watchOverflow: true,
		navigation: {
			nextEl: '.trainers-swiper-button-next',
			prevEl: '.trainers-swiper-button-prev',
		},
		breakpoints: {
			640: {
				slidesPerView: 2,
				spaceBetween: 16,
			},
			1024: {
				slidesPerView: 2,
				spaceBetween: 20,
			},
			1280: {
				slidesPerView: 3,
				spaceBetween: 24,
			},
		},
	});
}

function initCoachSpotlightSwiper() {
	const elements = Array.from(document.querySelectorAll('[data-coach-spotlight-swiper]'));
	if (elements.length === 0) return;

	for (const element of elements) {
		// Avoid double init (e.g., hot reload)
		if (element.dataset.swiperInitialized === 'true') continue;
		element.dataset.swiperInitialized = 'true';

		const root = element.closest('[data-coach-spotlight]') ?? document;
		const nextEl = root.querySelector('[data-coach-spotlight-next]');
		const prevEl = root.querySelector('[data-coach-spotlight-prev]');

		new Swiper(element, {
			modules: [Navigation],
			loop: true,
			slidesPerView: 1,
			speed: 650,
			grabCursor: true,
			watchOverflow: true,
			navigation: {
				nextEl,
				prevEl,
			},
		});
	}
}

function initParallaxTranslate() {
	const elements = Array.from(document.querySelectorAll('[data-parallax]'));
	if (elements.length === 0) return;

	const prefersReducedMotion =
		window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
	if (prefersReducedMotion) return;

	let ticking = false;

	const clamp = (value, min, max) => Math.min(max, Math.max(min, value));

	const update = () => {
		ticking = false;

		const viewportHeight = window.innerHeight || 0;
		const viewportCenter = viewportHeight / 2;

		for (const el of elements) {
			const rect = el.getBoundingClientRect();
			if (rect.bottom < 0 || rect.top > viewportHeight) continue;

			const elCenter = rect.top + rect.height / 2;
			const travel = viewportCenter + rect.height / 2;
			const normalized = travel === 0 ? 0 : clamp((elCenter - viewportCenter) / travel, -1, 1);

			const maxX = Number(el.dataset.parallaxX || 0);
			const maxY = Number(el.dataset.parallaxY || 0);

			const x = -normalized * maxX;
			const y = -normalized * maxY;

			el.style.transform = `translate3d(${x.toFixed(2)}px, ${y.toFixed(2)}px, 0)`;
		}
	};

	const onScrollOrResize = () => {
		if (ticking) return;
		ticking = true;
		window.requestAnimationFrame(update);
	};

	window.addEventListener('scroll', onScrollOrResize, { passive: true });
	window.addEventListener('resize', onScrollOrResize);

	update();
}

function initCourseWeekPanels() {
	const root = document.querySelector('[data-course-week-panels]');
	if (!root) return;

	const overlay = root.querySelector('[data-panel-overlay]');
	const coursePanel = root.querySelector('[data-course-panel]');
	const coachPanel = root.querySelector('[data-coach-panel]');
	const courseBody = root.querySelector('[data-course-panel-body]');
	const coachBody = root.querySelector('[data-coach-panel-body]');
	const courseFullscreen = root.querySelector('[data-panel-fullscreen-course]');
	const coachFullscreen = root.querySelector('[data-panel-fullscreen-coach]');
	const closeButtons = Array.from(root.querySelectorAll('[data-panel-close]'));

	if (!overlay || !coursePanel || !coachPanel || !courseBody || !coachBody) return;

	const setBodyScrollLocked = (locked) => {
		document.documentElement.classList.toggle('overflow-hidden', locked);
		document.body.classList.toggle('overflow-hidden', locked);
	};

	const closeAll = () => {
		overlay.classList.add('hidden');
		coursePanel.classList.remove('translate-x-0');
		coursePanel.classList.add('translate-x-full');
		coachPanel.classList.remove('translate-x-0');
		coachPanel.classList.add('-translate-x-full');
		if (courseFullscreen) courseFullscreen.setAttribute('href', '#');
		if (coachFullscreen) coachFullscreen.setAttribute('href', '#');
		setBodyScrollLocked(false);
	};

	const openOverlay = () => {
		overlay.classList.remove('hidden');
		setBodyScrollLocked(true);
	};

	const loadInto = async (targetEl, url) => {
		if (!url) return;
		targetEl.setAttribute('aria-busy', 'true');
		targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-neutral-700">載入中...</div>';
		try {
			const res = await window.axios.get(url, { params: { partial: 1 } });
			targetEl.innerHTML = res.data;
		} catch (err) {
			targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-neutral-700">載入失敗，請稍後再試。</div>';
		} finally {
			targetEl.removeAttribute('aria-busy');
		}
	};

	const openCourse = async (url) => {
		openOverlay();
		coachPanel.classList.remove('translate-x-0');
		coachPanel.classList.add('-translate-x-full');
		coursePanel.classList.remove('translate-x-full');
		coursePanel.classList.add('translate-x-0');
		if (courseFullscreen && url) courseFullscreen.setAttribute('href', url);
		await loadInto(courseBody, url);
	};

	const openCoach = async (url) => {
		openOverlay();
		coursePanel.classList.remove('translate-x-0');
		coursePanel.classList.add('translate-x-full');
		coachPanel.classList.remove('-translate-x-full');
		coachPanel.classList.add('translate-x-0');
		if (coachFullscreen && url) coachFullscreen.setAttribute('href', url);
		await loadInto(coachBody, url);
	};

	overlay.addEventListener('click', closeAll);
	for (const btn of closeButtons) btn.addEventListener('click', closeAll);

	document.addEventListener('keydown', (e) => {
		if (e.key === 'Escape') closeAll();
	});

	const cards = Array.from(document.querySelectorAll('[data-course-card][data-course-url]'));
	for (const card of cards) {
		card.addEventListener('click', () => openCourse(card.dataset.courseUrl));
		card.addEventListener('keydown', (e) => {
			if (e.key !== 'Enter' && e.key !== ' ') return;
			e.preventDefault();
			openCourse(card.dataset.courseUrl);
		});
	}

	const coachTriggers = Array.from(document.querySelectorAll('[data-coach-trigger][data-coach-url]'));
	for (const trigger of coachTriggers) {
		trigger.addEventListener('click', (e) => {
			e.stopPropagation();
			openCoach(trigger.dataset.coachUrl);
		});
	}
}

function initSpaCoursePanels() {
	const root = document.querySelector('[data-spa-course-panels]');
	if (!root) return;

	// Avoid double init (e.g., hot reload)
	if (root.dataset.spaCoursePanelsInitialized === 'true') return;
	root.dataset.spaCoursePanelsInitialized = 'true';

	const overlay = root.querySelector('[data-spa-course-overlay]');
	const panel = root.querySelector('[data-spa-course-panel]');
	const body = root.querySelector('[data-spa-course-panel-body]');
	const fullscreen = root.querySelector('[data-spa-course-fullscreen]');
	const closeButtons = Array.from(root.querySelectorAll('[data-spa-course-close]'));

	if (!overlay || !panel || !body) return;

	const setBodyScrollLocked = (locked) => {
		document.documentElement.classList.toggle('overflow-hidden', locked);
		document.body.classList.toggle('overflow-hidden', locked);
	};

	const close = () => {
		overlay.classList.add('hidden');
		panel.classList.remove('translate-x-0');
		panel.classList.add('-translate-x-full');
		if (fullscreen) fullscreen.setAttribute('href', '#');
		setBodyScrollLocked(false);
	};

	const openOverlay = () => {
		overlay.classList.remove('hidden');
		setBodyScrollLocked(true);
	};

	const loadInto = async (targetEl, url) => {
		if (!url) return;
		targetEl.setAttribute('aria-busy', 'true');
		targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-white/70">載入中...</div>';
		try {
			const res = await window.axios.get(url, { params: { partial: 1 } });
			targetEl.innerHTML = res.data;
		} catch (err) {
			targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-white/70">載入失敗，請稍後再試。</div>';
		} finally {
			targetEl.removeAttribute('aria-busy');
		}
	};

	const openCourse = async (url) => {
		openOverlay();
		panel.classList.remove('-translate-x-full');
		panel.classList.add('translate-x-0');
		if (fullscreen && url) fullscreen.setAttribute('href', url);
		await loadInto(body, url);
	};

	overlay.addEventListener('click', close);
	for (const btn of closeButtons) btn.addEventListener('click', close);

	document.addEventListener('keydown', (e) => {
		if (e.key === 'Escape') close();
	});

	const openers = Array.from(
		document.querySelectorAll('[data-spa-course-open][data-spa-course-url]'),
	);
	for (const opener of openers) {
		opener.addEventListener('click', (e) => {
			e.preventDefault();
			e.stopPropagation();
			openCourse(opener.dataset.spaCourseUrl);
		});

		opener.addEventListener('keydown', (e) => {
			if (e.key !== 'Enter' && e.key !== ' ') return;
			e.preventDefault();
			e.stopPropagation();
			openCourse(opener.dataset.spaCourseUrl);
		});
	}
}

function initSpaNewsPanels() {
	const root = document.querySelector('[data-spa-news-panels]');
	if (!root) return;

	// Avoid double init (e.g., hot reload)
	if (root.dataset.spaNewsPanelsInitialized === 'true') return;
	root.dataset.spaNewsPanelsInitialized = 'true';

	const overlay = root.querySelector('[data-spa-news-overlay]');
	const panel = root.querySelector('[data-spa-news-panel]');
	const body = root.querySelector('[data-spa-news-panel-body]');
	const fullscreen = root.querySelector('[data-spa-news-fullscreen]');
	const closeButtons = Array.from(root.querySelectorAll('[data-spa-news-close]'));

	if (!overlay || !panel || !body) return;

	const setBodyScrollLocked = (locked) => {
		document.documentElement.classList.toggle('overflow-hidden', locked);
		document.body.classList.toggle('overflow-hidden', locked);
	};

	const close = () => {
		overlay.classList.add('hidden');
		panel.classList.remove('translate-x-0');
		panel.classList.add('translate-x-full');
		if (fullscreen) fullscreen.setAttribute('href', '#');
		setBodyScrollLocked(false);
	};

	const openOverlay = () => {
		overlay.classList.remove('hidden');
		setBodyScrollLocked(true);
	};

	const loadInto = async (targetEl, url) => {
		if (!url) return;
		targetEl.setAttribute('aria-busy', 'true');
		targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-neutral-700">載入中...</div>';
		try {
			const res = await window.axios.get(url, { params: { partial: 1 } });
			targetEl.innerHTML = res.data;
		} catch (err) {
			targetEl.innerHTML = '<div class="px-6 py-12 md:px-12 text-neutral-700">載入失敗，請稍後再試。</div>';
		} finally {
			targetEl.removeAttribute('aria-busy');
		}
	};

	const openNews = async (url) => {
		openOverlay();
		panel.classList.remove('translate-x-full');
		panel.classList.add('translate-x-0');
		if (fullscreen && url) fullscreen.setAttribute('href', url);
		await loadInto(body, url);
	};

	overlay.addEventListener('click', close);
	for (const btn of closeButtons) btn.addEventListener('click', close);

	document.addEventListener('keydown', (e) => {
		if (e.key === 'Escape') close();
	});

	const openers = Array.from(document.querySelectorAll('[data-spa-news-open][data-spa-news-url]'));
	for (const opener of openers) {
		opener.addEventListener('click', (e) => {
			e.preventDefault();
			e.stopPropagation();
			openNews(opener.dataset.spaNewsUrl);
		});

		opener.addEventListener('keydown', (e) => {
			if (e.key !== 'Enter' && e.key !== ' ') return;
			e.preventDefault();
			e.stopPropagation();
			openNews(opener.dataset.spaNewsUrl);
		});
	}
}

function initFilterButtons({
	rootSelector,
	buttonSelector,
	itemSelector,
	getItemCats,
	activeButtonClass,
	inactiveButtonClass,
	activeButtonClasses,
	inactiveButtonClasses,
	hideItemClasses,
	transitionMs,
}) {
	const root = document.querySelector(rootSelector);
	if (!root) return;

	// Avoid double init (e.g., hot reload or multiple bundles)
	if (root.dataset.filterInitialized === 'true') return;
	root.dataset.filterInitialized = 'true';

	const buttons = Array.from(root.querySelectorAll(buttonSelector));
	const items = Array.from(root.querySelectorAll(itemSelector));
	if (buttons.length === 0 || items.length === 0) return;

	const normalizeClasses = (value) => {
		if (!value) return [];
		return Array.isArray(value) ? value.filter(Boolean) : [value];
	};

	const activeClasses = [
		...normalizeClasses(activeButtonClass),
		...normalizeClasses(activeButtonClasses),
	];
	const inactiveClasses = [
		...normalizeClasses(inactiveButtonClass),
		...normalizeClasses(inactiveButtonClasses),
	];

	const hiddenClasses = normalizeClasses(hideItemClasses);
	const duration = Number.isFinite(transitionMs) ? transitionMs : 700;
	let runId = 0;

	const prefersReducedMotion =
		window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

	const setHiddenInstant = (el, shouldHide) => {
		el.classList.toggle('hidden', shouldHide);
		for (const c of hiddenClasses) el.classList.toggle(c, shouldHide);
		el.style.transitionDelay = '';
		el.style.transform = '';
	};

	const applyStagger = (els, { reverse = false, step = 45, max = 280 } = {}) => {
		let longest = 0;
		const len = els.length;
		for (let i = 0; i < len; i++) {
			const index = reverse ? len - 1 - i : i;
			const delay = Math.min(index * step, max);
			els[i].style.transitionDelay = `${delay}ms`;
			if (delay > longest) longest = delay;
		}
		return longest;
	};

	const setActiveButtonByValue = (activeValue) => {
		for (const btn of buttons) {
			const isActive = (btn.dataset.filter || '') === activeValue;
			btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
			for (const c of activeClasses) btn.classList.toggle(c, isActive);
			for (const c of inactiveClasses) btn.classList.toggle(c, !isActive);
		}
	};

	const animateFilter = (value) => {
		const next = String(value || 'ALL').trim() || 'ALL';
		runId++;
		const myRunId = runId;
		root.dataset.currentFilter = next;

		if (prefersReducedMotion) {
			for (const el of items) {
				const cats = getItemCats(el);
				const shouldShow = next === 'ALL' || cats.includes(next);
				setHiddenInstant(el, !shouldShow);
			}
			setActiveButtonByValue(next);
			return;
		}

		for (const el of items) {
			el.style.transitionDelay = '';
			el.style.transform = '';
		}

		// FLIP: record first positions of currently visible items
		const first = new Map();
		for (const el of items) {
			if (el.classList.contains('hidden')) continue;
			first.set(el, el.getBoundingClientRect());
		}

		const toHide = [];
		const toShow = [];
		for (const el of items) {
			const cats = getItemCats(el);
			const shouldShow = next === 'ALL' || cats.includes(next);
			const isHidden = el.classList.contains('hidden');
			if (shouldShow && isHidden) toShow.push(el);
			if (!shouldShow && !isHidden) toHide.push(el);
		}

		// Prepare entering elements (unhide but keep them visually hidden for the first frame)
		for (const el of toShow) {
			el.classList.remove('hidden');
			if (hiddenClasses.length) el.classList.add(...hiddenClasses);
		}

		// Prepare leaving elements (fade/scale out immediately)
		for (const el of toHide) {
			if (hiddenClasses.length) el.classList.add(...hiddenClasses);
		}

		// Stagger: let exiting items start slightly earlier for snappier feel
		const exitLongestDelay = applyStagger(toHide, { reverse: true, step: 35, max: 210 });

		// Force layout so the browser acknowledges the new grid
		root.getBoundingClientRect();

		// Compute last positions for visible items and invert transforms
		const last = new Map();
		const nowVisible = items.filter((el) => !el.classList.contains('hidden'));
		for (const el of nowVisible) {
			last.set(el, el.getBoundingClientRect());
		}

		for (const el of nowVisible) {
			const firstRect = first.get(el);
			const lastRect = last.get(el);
			if (!lastRect) continue;
			if (firstRect) {
				const dx = firstRect.left - lastRect.left;
				const dy = firstRect.top - lastRect.top;
				if (dx || dy) el.style.transform = `translate(${dx}px, ${dy}px)`;
			}
		}

		// Stagger: movement + entering items cascade
		const moveLongestDelay = applyStagger(nowVisible, { reverse: false, step: 45, max: 280 });
		const enterLongestDelay = applyStagger(toShow, { reverse: false, step: 55, max: 330 });
		const longestDelay = Math.max(exitLongestDelay, moveLongestDelay, enterLongestDelay);

		// Play: remove transforms and fade in entering items
		window.requestAnimationFrame(() => {
			for (const el of nowVisible) {
				el.style.transform = '';
			}

			for (const el of toShow) {
				if (hiddenClasses.length) el.classList.remove(...hiddenClasses);
			}

			// After the transition, fully hide leaving items and cleanup
			const cleanupHide = (el) => {
				if (myRunId !== runId) return;
				el.classList.add('hidden');
				if (hiddenClasses.length) el.classList.remove(...hiddenClasses);
				el.style.transitionDelay = '';
				el.style.transform = '';
				el.removeEventListener('transitionend', onEnd);
			};

			const onEnd = (ev) => {
				if (ev.propertyName !== 'opacity') return;
				cleanupHide(ev.currentTarget);
			};

			for (const el of toHide) {
				el.addEventListener('transitionend', onEnd);
				// Safety: in case transitionend doesn't fire (e.g., tab switch)
				window.setTimeout(() => {
					if (myRunId !== runId) return;
					if (!el.classList.contains('hidden')) cleanupHide(el);
				}, duration + 400 + longestDelay);
			}

			// Cleanup delays for visible items after motion completes
			window.setTimeout(() => {
				if (myRunId !== runId) return;
				for (const el of items) {
					if (!el.classList.contains('hidden')) el.style.transitionDelay = '';
				}
			}, duration + 200 + longestDelay);
		});

		setActiveButtonByValue(next);
	};

	for (const btn of buttons) {
		btn.addEventListener('click', () => {
			animateFilter(btn.dataset.filter || 'ALL');
		});
	}

	// Initial state
	const initial = buttons.find((b) => b.getAttribute('aria-pressed') === 'true') || buttons[0];
	animateFilter(initial?.dataset.filter || 'ALL');
}

function initCoachListFilters() {
	initFilterButtons({
		rootSelector: '[data-coach-list-root]',
		buttonSelector: '[data-coach-filter-button][data-filter]',
		itemSelector: '[data-coach-item][data-cats]',
		getItemCats: (item) => String(item.dataset.cats || '').split('|').filter(Boolean),
		activeButtonClasses: ['bg-black/55', 'text-white'],
		inactiveButtonClasses: ['bg-white/10', 'text-white/80'],
		hideItemClasses: ['opacity-0', 'scale-95', 'pointer-events-none'],
		transitionMs: 700,
	});
}

function initFacilityGalleryFilters() {
	initFilterButtons({
		rootSelector: '[data-facility-gallery-root]',
		buttonSelector: '[data-facility-filter-button][data-filter]',
		itemSelector: '[data-facility-item][data-cat]',
		getItemCats: (item) => {
			const cat = String(item.dataset.cat || '').trim();
			return cat ? [cat] : [];
		},
		activeButtonClasses: ['bg-black/55', 'text-white'],
		inactiveButtonClasses: ['bg-white/10', 'text-white/80'],
		hideItemClasses: ['opacity-0', 'scale-95', 'pointer-events-none'],
		transitionMs: 700,
	});
}

function initFacilityGalleryFancybox() {
	const root = document.querySelector('[data-facility-gallery-root]');
	if (!root) return;

	// Avoid double init
	if (root.dataset.fancyboxInitialized === 'true') return;
	root.dataset.fancyboxInitialized = 'true';

	const items = Array.from(root.querySelectorAll('[data-facility-item]'));
	if (items.length === 0) return;

	for (const el of items) {
		el.addEventListener('click', (e) => {
			if (!window.Fancybox || typeof window.Fancybox.show !== 'function') return;
			e.preventDefault();

			const visibleItems = items.filter((x) => !x.classList.contains('hidden'));
			const startIndex = Math.max(0, visibleItems.indexOf(el));

			const slides = visibleItems.map((a) => ({
				src: a.getAttribute('href'),
				type: 'image',
				caption: a.dataset.caption || '',
			}));

			const currentFilter = String(root.dataset.currentFilter || 'ALL');
			window.Fancybox.show(slides, {
				startIndex,
				hideScrollbar: true,
				groupAll: false,
				mainClass: currentFilter === 'ALL' ? 'facility-gallery-all' : 'facility-gallery-filtered',
			});
		});
	}
}

function initSpaHeroGridCursor() {
	const hero = document.querySelector('.spa-hero');
	if (!hero) return;

	const grid = hero.querySelector('.spa-hero-grid');
	if (!grid) return;

	// Disable on touch devices & reduced motion.
	const prefersReducedMotion =
		window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
	if (prefersReducedMotion) return;
	if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) return;

	const rows = Array.from(grid.querySelectorAll('.row'));
	if (rows.length === 0) return;

	let rect = hero.getBoundingClientRect();
	let rafId = 0;
	let targetX = 0;
	let targetY = 0;

	const maxX = 42;
	const maxY = 18;

	const updateRect = () => {
		rect = hero.getBoundingClientRect();
	};

	const clamp = (value, min, max) => Math.min(max, Math.max(min, value));

	const apply = () => {
		rafId = 0;

		// Normalize cursor to [-1, 1] around hero center.
		const cx = rect.left + rect.width / 2;
		const cy = rect.top + rect.height / 2;
		const nx = rect.width ? clamp((targetX - cx) / (rect.width / 2), -1, 1) : 0;
		const ny = rect.height ? clamp((targetY - cy) / (rect.height / 2), -1, 1) : 0;

		rows.forEach((row, index) => {
			const depth = 1 - index * 0.12;
			const dir = index % 2 === 0 ? 1 : -1;
			const x = nx * maxX * depth * dir;
			const y = ny * maxY * depth;
			row.style.transform = `translate3d(${x.toFixed(2)}px, ${y.toFixed(2)}px, 0)`;
		});
	};

	const schedule = () => {
		if (rafId) return;
		rafId = window.requestAnimationFrame(apply);
	};

	const onMove = (e) => {
		targetX = e.clientX;
		targetY = e.clientY;
		schedule();
	};

	const onEnter = () => {
		hero.classList.remove('spa-hero--resetting');
		updateRect();
	};

	const onLeave = () => {
		hero.classList.add('spa-hero--resetting');
		for (const row of rows) row.style.transform = 'translate3d(0, 0, 0)';
		window.setTimeout(() => hero.classList.remove('spa-hero--resetting'), 360);
	};

	const onResize = () => {
		updateRect();
	};

	hero.addEventListener('mouseenter', onEnter);
	hero.addEventListener('mousemove', onMove);
	hero.addEventListener('mouseleave', onLeave);
	window.addEventListener('resize', onResize);

	// Initial rect for pages loaded scrolled.
	updateRect();
}

document.addEventListener('DOMContentLoaded', () => {
	initNewsSwiper();
	initFacilitiesSwiper();
	initTrainersSwiper();
	initCoachSpotlightSwiper();
	initParallaxTranslate();
	initSpaHeroGridCursor();
	initCourseWeekPanels();
	initSpaCoursePanels();
	initSpaNewsPanels();
	initFacilityGalleryFilters();
	initFacilityGalleryFancybox();
	initCoachListFilters();
});
