const translations = window.translations; const languageLocales = { vi: 'vi_VN', ru: 'ru_RU', en: 'en_US', zh: 'zh_CN', de: 'de_DE', fr: 'fr_FR' }; let currentLang = 'vi'; let currentWeatherCondition = 'weather_loading'; let tomorrowWeatherCondition = 'weather_loading'; function getWeatherPresentation(code) { if (code === 0) return { key: 'weather_clear', icon: 'fa-sun', color: 'text-amber-500' }; if (code === 1) return { key: 'weather_mainly_clear', icon: 'fa-cloud-sun', color: 'text-amber-500' }; if (code === 2) return { key: 'weather_partly_cloudy', icon: 'fa-cloud-sun', color: 'text-amber-500' }; if (code === 3) return { key: 'weather_overcast', icon: 'fa-cloud', color: 'text-gray-500' }; if ([45, 48].includes(code)) return { key: 'weather_fog', icon: 'fa-smog', color: 'text-gray-500' }; if ([51, 53, 55, 56, 57].includes(code)) return { key: 'weather_drizzle', icon: 'fa-cloud-rain', color: 'text-sky-500' }; if ([61, 63, 65, 66, 67].includes(code)) return { key: 'weather_rain', icon: 'fa-cloud-rain', color: 'text-sky-600' }; if ([71, 73, 75, 77].includes(code)) return { key: 'weather_snow', icon: 'fa-snowflake', color: 'text-sky-500' }; if ([80, 81, 82, 85, 86].includes(code)) return { key: 'weather_showers', icon: 'fa-cloud-showers-heavy', color: 'text-sky-600' }; if ([95, 96, 99].includes(code)) return { key: 'weather_thunderstorm', icon: 'fa-cloud-bolt', color: 'text-indigo-500' }; return { key: 'weather_partly_cloudy', icon: 'fa-cloud-sun', color: 'text-amber-500' }; } function renderWeatherCondition() { const condition = document.getElementById('weather-condition'); if (condition) condition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition]; const currentForecastCondition = document.getElementById('weather-current-condition'); if (currentForecastCondition) { currentForecastCondition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition]; } const tomorrowForecastCondition = document.getElementById('weather-tomorrow-condition'); if (tomorrowForecastCondition) { tomorrowForecastCondition.textContent = translations[currentLang][tomorrowWeatherCondition] || translations.en[tomorrowWeatherCondition]; } } function setWeatherRange(id, value) { const element = document.getElementById(id); if (element) element.textContent = Number.isFinite(value) ? `${Math.round(value)}°` : '--°'; } function setWeatherIcon(id, presentation) { const icon = document.getElementById(id); if (icon) icon.className = `fa-solid ${presentation.icon} text-4xl ${presentation.color}`; } async function loadWeatherForecast() { const temperature = document.getElementById('weather-temperature'); const icon = document.getElementById('weather-icon'); const currentForecastTemperature = document.getElementById('weather-current-temperature'); const tomorrowDate = document.getElementById('weather-tomorrow-date'); try { const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=12.2388&longitude=109.1967¤t=temperature_2m,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=Asia%2FHo_Chi_Minh&forecast_days=2'); if (!response.ok) throw new Error('Weather request failed'); const data = await response.json(); const current = data.current; const daily = data.daily; if ( !Number.isFinite(current?.temperature_2m) || !Number.isFinite(current?.weather_code) || !Array.isArray(daily?.time) || !Array.isArray(daily?.weather_code) || !Array.isArray(daily?.temperature_2m_max) || !Array.isArray(daily?.temperature_2m_min) || daily.time.length < 2 || daily.weather_code.length < 2 || daily.temperature_2m_max.length < 2 || daily.temperature_2m_min.length < 2 || !Number.isFinite(daily.weather_code[1]) || !Number.isFinite(daily.temperature_2m_max[0]) || !Number.isFinite(daily.temperature_2m_min[0]) || !Number.isFinite(daily.temperature_2m_max[1]) || !Number.isFinite(daily.temperature_2m_min[1]) || !/^\d{4}-\d{2}-\d{2}$/.test(daily.time[1]) ) { throw new Error('Weather response is incomplete'); } const currentPresentation = getWeatherPresentation(current.weather_code); const tomorrowPresentation = getWeatherPresentation(daily.weather_code[1]); const currentTemperature = `${Math.round(current.temperature_2m)}°C`; if (temperature) temperature.textContent = currentTemperature; if (currentForecastTemperature) currentForecastTemperature.textContent = currentTemperature; if (icon) icon.className = `fa-solid ${currentPresentation.icon} text-2xl ${currentPresentation.color}`; setWeatherIcon('weather-current-icon', currentPresentation); setWeatherIcon('weather-tomorrow-icon', tomorrowPresentation); setWeatherRange('weather-today-high', daily.temperature_2m_max[0]); setWeatherRange('weather-today-low', daily.temperature_2m_min[0]); setWeatherRange('weather-tomorrow-high', daily.temperature_2m_max[1]); setWeatherRange('weather-tomorrow-low', daily.temperature_2m_min[1]); if (tomorrowDate) { const [year, month, day] = daily.time[1].split('-').map(Number); const date = new Date(year, month - 1, day, 12); tomorrowDate.dateTime = daily.time[1]; tomorrowDate.textContent = new Intl.DateTimeFormat(languageLocales[currentLang].replace('_', '-'), { weekday: 'short', month: 'short', day: 'numeric' }).format(date); } currentWeatherCondition = currentPresentation.key; tomorrowWeatherCondition = tomorrowPresentation.key; } catch { if (temperature) temperature.textContent = '--°C'; if (currentForecastTemperature) currentForecastTemperature.textContent = '--°C'; if (icon) icon.className = 'fa-solid fa-cloud text-2xl text-gray-400'; setWeatherIcon('weather-current-icon', { icon: 'fa-cloud', color: 'text-gray-400' }); setWeatherIcon('weather-tomorrow-icon', { icon: 'fa-cloud', color: 'text-gray-400' }); setWeatherRange('weather-today-high', NaN); setWeatherRange('weather-today-low', NaN); setWeatherRange('weather-tomorrow-high', NaN); setWeatherRange('weather-tomorrow-low', NaN); if (tomorrowDate) { tomorrowDate.textContent = ''; tomorrowDate.removeAttribute('datetime'); } currentWeatherCondition = 'weather_unavailable'; tomorrowWeatherCondition = 'weather_unavailable'; } renderWeatherCondition(); } function getPreferredLanguage() { const supported = Object.keys(translations); const preferences = navigator.languages?.length ? navigator.languages : [navigator.language]; for (const locale of preferences) { const language = locale.toLowerCase().split(/[-_]/)[0]; if (supported.includes(language)) return language; } return 'vi'; } function getRequestedLanguage() { const requested = new URLSearchParams(window.location.search).get('lang'); return translations[requested] ? requested : null; } function setLanguage(lang) { if (!translations[lang]) lang = 'vi'; currentLang = lang; document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); if (translations[lang] && translations[lang][key]) { el.textContent = translations[lang][key]; } }); // Reset language button styles const langs = ['vi', 'ru', 'en', 'zh', 'de', 'fr']; langs.forEach(l => { const btn = document.getElementById(`btn-${l}`); if (btn) { if (l === lang) { btn.className = "px-2.5 py-1.5 font-bold rounded-lg transition bg-white text-brand-600 shadow-sm"; } else { btn.className = "px-2.5 py-1.5 font-bold rounded-lg transition text-gray-600 hover:text-brand-600"; } } }); document.documentElement.lang = lang; const languageUrl = new URL(window.location.href); languageUrl.searchParams.set('lang', lang); history.replaceState(null, '', `${languageUrl.pathname}${languageUrl.search}${languageUrl.hash}`); const canonicalUrl = new URL(`/?lang=${lang}`, window.location.origin).href; document.getElementById('canonical-url').href = canonicalUrl; document.getElementById('meta-description').content = translations[lang].meta_description; document.getElementById('og-title').content = translations[lang].page_title; document.getElementById('og-description').content = translations[lang].meta_description; document.getElementById('og-url').content = canonicalUrl; document.getElementById('og-locale').content = languageLocales[lang]; renderWeatherCondition(); const tomorrowDate = document.getElementById('weather-tomorrow-date'); if (tomorrowDate.dateTime) { const [year, month, day] = tomorrowDate.dateTime.split('-').map(Number); tomorrowDate.textContent = new Intl.DateTimeFormat(languageLocales[lang].replace('_', '-'), { weekday: 'short', month: 'short', day: 'numeric' }).format(new Date(year, month - 1, day, 12)); } } function toggleMobileMenu() { const menu = document.getElementById('mobile-menu'); menu.classList.toggle('hidden'); document.querySelector('[aria-controls="mobile-menu"]').setAttribute('aria-expanded', !menu.classList.contains('hidden')); } function filterAttractions(category, event) { document.querySelectorAll('.filter-btn').forEach(btn => { btn.className = "filter-btn px-5 py-2 rounded-xl text-sm font-bold bg-white text-gray-600 hover:bg-gray-100 shadow-sm"; }); event.currentTarget.className = "filter-btn active px-5 py-2 rounded-xl text-sm font-bold bg-brand-500 text-white shadow"; document.querySelectorAll('.attraction-card').forEach(card => { if (category === 'all' || card.getAttribute('data-category') === category) { card.style.display = 'flex'; } else { card.style.display = 'none'; } }); } async function handleBooking(event) { event.preventDefault(); const form = event.currentTarget; const submitButton = form.querySelector('[type="submit"]'); const status = document.getElementById('form-status'); status.classList.add('hidden'); submitButton.disabled = true; try { const response = await fetch(form.action, { method: form.method, body: new FormData(form), headers: { Accept: 'application/json' } }); if (!response.ok) throw new Error('Form submission failed'); form.reset(); document.getElementById('success-modal').classList.remove('hidden'); } catch { status.classList.remove('hidden'); } finally { submitButton.disabled = false; } } function closeModal() { document.getElementById('success-modal').classList.add('hidden'); } setLanguage(getRequestedLanguage() || getPreferredLanguage()); loadWeatherForecast(); setInterval(loadWeatherForecast, 30 * 60 * 1000);