diff --git a/index.html b/index.html
index 60c86a6..8504ac4 100755
--- a/index.html
+++ b/index.html
@@ -376,6 +376,38 @@
Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.
+
diff --git a/js/main.js b/js/main.js
index b8d4aea..2e05f8c 100644
--- a/js/main.js
+++ b/js/main.js
@@ -11,6 +11,7 @@ const languageLocales = {
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' };
@@ -29,25 +30,104 @@ function getWeatherPresentation(code) {
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];
+ }
}
-async function loadCurrentWeather() {
+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&timezone=Asia%2FHo_Chi_Minh');
+ 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 presentation = getWeatherPresentation(data.current.weather_code);
- temperature.textContent = `${Math.round(data.current.temperature_2m)}°C`;
- icon.className = `fa-solid ${presentation.icon} text-2xl ${presentation.color}`;
- currentWeatherCondition = presentation.key;
+ 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 {
- temperature.textContent = '--°C';
- icon.className = 'fa-solid fa-cloud text-2xl text-gray-400';
+ 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();
@@ -108,6 +188,16 @@ function setLanguage(lang) {
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() {
@@ -161,5 +251,5 @@ function closeModal() {
}
setLanguage(getRequestedLanguage() || getPreferredLanguage());
-loadCurrentWeather();
-setInterval(loadCurrentWeather, 30 * 60 * 1000);
+loadWeatherForecast();
+setInterval(loadWeatherForecast, 30 * 60 * 1000);
diff --git a/js/translations.js b/js/translations.js
index 6b63c21..221dce9 100644
--- a/js/translations.js
+++ b/js/translations.js
@@ -61,6 +61,10 @@ window.translations = {
attr_9_loc: "Vĩnh Hy, Ninh Hải",
weather_title: "Thời điểm lý tưởng du lịch",
weather_desc: "Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.",
+ weather_current: "Thời tiết hiện tại",
+ weather_tomorrow: "Ngày mai",
+ weather_high: "Cao nhất",
+ weather_low: "Thấp nhất",
weather_loading: "Đang tải thời tiết...",
weather_unavailable: "Không có dữ liệu",
weather_clear: "Trời quang",
@@ -166,6 +170,10 @@ window.translations = {
attr_9_loc: "Виньхи, Ниньхай",
weather_title: "Лучшее время для поездки",
weather_desc: "Спланируйте свою поездку на основе точных метеорологических данных.",
+ weather_current: "Сейчас",
+ weather_tomorrow: "Завтра",
+ weather_high: "Макс.",
+ weather_low: "Мин.",
weather_loading: "Загрузка погоды...",
weather_unavailable: "Нет данных",
weather_clear: "Ясно",
@@ -271,6 +279,10 @@ window.translations = {
attr_9_loc: "Vĩnh Hy, Ninh Hải",
weather_title: "Best Time to Visit",
weather_desc: "Plan your trip with accurate seasonal weather insights.",
+ weather_current: "Current weather",
+ weather_tomorrow: "Tomorrow",
+ weather_high: "High",
+ weather_low: "Low",
weather_loading: "Loading weather...",
weather_unavailable: "Unavailable",
weather_clear: "Clear",
@@ -376,6 +388,10 @@ window.translations = {
attr_9_loc: "宁海县永希",
weather_title: "最佳旅游季节",
weather_desc: "根据精准气候指南,轻松规划您的完美行程。",
+ weather_current: "当前天气",
+ weather_tomorrow: "明天",
+ weather_high: "最高",
+ weather_low: "最低",
weather_loading: "正在加载天气...",
weather_unavailable: "暂无数据",
weather_clear: "晴朗",
@@ -481,6 +497,10 @@ window.translations = {
attr_9_loc: "Vĩnh Hy, Ninh Hải",
weather_title: "Beste Reisezeit",
weather_desc: "Planen Sie Ihre Reise mit verlässlichen Informationen zum saisonalen Wetter.",
+ weather_current: "Aktuelles Wetter",
+ weather_tomorrow: "Morgen",
+ weather_high: "Höchstwert",
+ weather_low: "Tiefstwert",
weather_loading: "Wetter wird geladen...",
weather_unavailable: "Keine Daten",
weather_clear: "Klar",
@@ -586,6 +606,10 @@ window.translations = {
attr_9_loc: "Vĩnh Hy, Ninh Hải",
weather_title: "Quand partir ?",
weather_desc: "Planifiez votre séjour grâce à nos informations sur le climat selon les saisons.",
+ weather_current: "Météo actuelle",
+ weather_tomorrow: "Demain",
+ weather_high: "Max.",
+ weather_low: "Min.",
weather_loading: "Chargement de la météo...",
weather_unavailable: "Données indisponibles",
weather_clear: "Ciel dégagé",