Weather/script.js

1142 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// API ключ от WeatherAPI
const API_KEY = "485eff906f7d473b913104046250710";
// Yandex Metrika counter ID (замените на свой)
const YM_COUNTER_ID = 104563496; // Замените на реальный ID счетчика
// Функция для отправки событий в Yandex Metrika
function ymSendEvent(eventName, params = {}) {
if (typeof ym !== 'undefined' && YM_COUNTER_ID !== 'YOUR_COUNTER_ID') {
ym(YM_COUNTER_ID, 'reachGoal', eventName, params);
console.log(`Yandex Metrika event: ${eventName}`, params);
}
}
// Глобальные переменные для темы
const themeButtons = {
light: document.getElementById("theme-light"),
auto: document.getElementById("theme-auto"),
dark: document.getElementById("theme-dark"),
};
const citySelect = document.getElementById("city");
const getWeatherBtn = document.getElementById("get-weather");
const timelapseContainer = document.getElementById("timelapse-container");
const currentWeatherDiv = document.getElementById("current-weather");
const currentTempEl = document.getElementById("current-temp");
const currentDescEl = document.getElementById("current-desc");
const currentHumidityEl = document.getElementById("current-humidity");
const currentWindEl = document.getElementById("current-wind");
// Глобальная переменная для Swiper
let weatherSwiper;
// Глобальные переменные для графиков
let temperatureChart = null;
let precipitationChart = null;
// Иконки погоды
const weatherIcons = {
Sunny: "sun",
Clear: "sun",
"Partly cloudy": "cloud-sun",
Cloudy: "cloud",
Overcast: "cloud",
Mist: "cloud-fog",
Fog: "cloud-fog",
"Light rain": "cloud-rain",
"Moderate rain": "cloud-rain",
"Heavy rain": "cloud-rain",
"Light snow": "cloud-snow",
"Moderate snow": "cloud-snow",
"Heavy snow": "cloud-snow",
Thunderstorm: "cloud-lightning",
"Patchy rain possible": "cloud-drizzle",
"Patchy snow possible": "cloud-snow",
"Patchy sleet possible": "cloud-snow",
"Patchy freezing drizzle possible": "cloud-drizzle",
"Blowing snow": "wind",
Blizzard: "wind",
"Freezing fog": "cloud-fog",
"Patchy light drizzle": "cloud-drizzle",
"Light drizzle": "cloud-drizzle",
"Freezing drizzle": "cloud-drizzle",
"Heavy freezing drizzle": "cloud-drizzle",
"Patchy light rain": "cloud-rain",
"Light rain shower": "cloud-rain",
"Moderate or heavy rain shower": "cloud-rain",
"Torrential rain shower": "cloud-rain",
"Patchy light snow": "cloud-snow",
"Light snow showers": "cloud-snow",
"Moderate or heavy snow showers": "cloud-snow",
"Patchy light rain with thunder": "cloud-lightning",
"Moderate or heavy rain with thunder": "cloud-lightning",
"Patchy light snow with thunder": "cloud-lightning",
"Moderate or heavy snow with thunder": "cloud-lightning",
// Ночные условия
"Clear night": "moon",
"Partly cloudy night": "cloud-moon",
"Cloudy night": "cloud",
"Sunny night": "moon",
"Light rain night": "cloud-rain",
"Moderate rain night": "cloud-rain",
"Heavy rain night": "cloud-rain",
"Light snow night": "cloud-snow",
"Moderate snow night": "cloud-snow",
"Heavy snow night": "cloud-snow",
"Thunderstorm night": "cloud-lightning",
"Patchy rain possible night": "cloud-drizzle",
"Patchy snow possible night": "cloud-snow",
"Mist night": "cloud-fog",
"Fog night": "cloud-fog",
// Дополнительные типы погоды
"Light sleet": "cloud-snow",
"Moderate or heavy sleet": "cloud-snow",
"Ice pellets": "cloud-hail",
"Light showers of ice pellets": "cloud-hail",
"Moderate or heavy showers of ice pellets": "cloud-hail",
Hail: "cloud-hail",
"Light hail": "cloud-hail",
"Moderate or heavy hail": "cloud-hail",
Tornado: "tornado",
Hurricane: "hurricane",
Squall: "wind",
Dust: "wind",
Sand: "wind",
"Volcanic ash": "wind",
"Thundery outbreaks possible": "cloud-lightning",
"Thundery outbreaks in nearby": "cloud-lightning",
};
// Маппинг русских погодных условий на английские ключи для иконок
const russianToEnglishWeather = {
Солнечно: "Sunny",
Ясно: "Clear",
"Частично облачно": "Partly cloudy",
Облачно: "Cloudy",
Пасмурно: "Overcast",
Туман: "Mist",
"Слабый туман": "Mist",
"Легкий дождь": "Light rain",
"Умеренный дождь": "Moderate rain",
"Сильный дождь": "Heavy rain",
"Легкий снег": "Light snow",
"Умеренный снег": "Moderate snow",
"Сильный снег": "Heavy snow",
Гроза: "Thunderstorm",
"Возможен небольшой дождь": "Patchy rain possible",
"Возможен небольшой снег": "Patchy snow possible",
"Местами дождь": "Patchy rain possible",
"Местами снег": "Patchy snow possible",
Морось: "Patchy light drizzle",
"Легкая морось": "Light drizzle",
"Замерзающая морось": "Freezing drizzle",
Ливень: "Light rain shower",
"Умеренный или сильный ливень": "Moderate or heavy rain shower",
"Снег с дождем": "Light sleet",
"Умеренный или сильный снег с дождем": "Moderate or heavy sleet",
Град: "Hail",
"Легкий град": "Light hail",
"Умеренный или сильный град": "Moderate or heavy hail",
Торнадо: "Tornado",
Ураган: "Hurricane",
Шквал: "Squall",
Пыль: "Dust",
Песок: "Sand",
"Вулканический пепел": "Volcanic ash",
"Возможны грозы": "Thundery outbreaks possible",
"Грозы в округе": "Thundery outbreaks in nearby",
// Ночные условия
"Ясно ночь": "Clear night",
"Частично облачно ночь": "Partly cloudy night",
"Облачно ночь": "Cloudy night",
"Солнечно ночь": "Sunny night",
"Легкий дождь ночь": "Light rain night",
"Умеренный дождь ночь": "Moderate rain night",
"Сильный дождь ночь": "Heavy rain night",
"Легкий снег ночь": "Light snow night",
"Умеренный снег ночь": "Moderate snow night",
"Сильный снег ночь": "Heavy snow night",
"Гроза ночь": "Thunderstorm night",
"Возможен небольшой дождь ночь": "Patchy rain possible night",
"Возможен небольшой снег ночь": "Patchy snow possible night",
"Туман ночь": "Mist night",
"Слабый туман ночь": "Fog night",
};
// Красивые русские названия для отображения
const beautifulRussianNames = {
Sunny: "Солнечно",
Clear: "Ясно",
"Partly cloudy": "Переменная облачность",
Cloudy: "Облачно",
Overcast: "Пасмурно",
Mist: "Туман",
Fog: "Густой туман",
"Light rain": "Небольшой дождь",
"Moderate rain": "Дождь",
"Heavy rain": "Сильный дождь",
"Light snow": "Небольшой снег",
"Moderate snow": "Снег",
"Heavy snow": "Сильный снег",
Thunderstorm: "Гроза",
"Patchy rain possible": "Местами дождь",
"Patchy snow possible": "Местами снег",
"Patchy sleet possible": "Местами мокрый снег",
"Patchy freezing drizzle possible": "Местами изморось",
"Blowing snow": "Поземок",
Blizzard: "Метель",
"Freezing fog": "Ледяной туман",
"Patchy light drizzle": "Морось",
"Light drizzle": "Легкая морось",
"Freezing drizzle": "Замерзающая морось",
"Heavy freezing drizzle": "Сильная изморось",
"Patchy light rain": "Кратковременный дождь",
"Light rain shower": "Ливень",
"Moderate or heavy rain shower": "Сильный ливень",
"Torrential rain shower": "Ливневой дождь",
"Patchy light snow": "Кратковременный снег",
"Light snow showers": "Снеговые заряды",
"Moderate or heavy snow showers": "Сильные снегопады",
"Patchy light rain with thunder": "Гроза с дождем",
"Moderate or heavy rain with thunder": "Гроза с сильным дождем",
"Patchy light snow with thunder": "Гроза со снегом",
"Moderate or heavy snow with thunder": "Гроза с сильным снегом",
"Clear night": "Ясная ночь",
"Partly cloudy night": "Переменная облачность ночью",
"Cloudy night": "Облачная ночь",
"Sunny night": "Солнечная ночь",
"Light rain night": "Дождь ночью",
"Moderate rain night": "Дождь ночью",
"Heavy rain night": "Сильный дождь ночью",
"Light snow night": "Снег ночью",
"Moderate snow night": "Снег ночью",
"Heavy snow night": "Сильный снег ночью",
"Thunderstorm night": "Гроза ночью",
"Patchy rain possible night": "Местами дождь ночью",
"Patchy snow possible night": "Местами снег ночью",
"Mist night": "Туман ночью",
"Fog night": "Густой туман ночью",
"Light sleet": "Мокрый снег",
"Moderate or heavy sleet": "Сильный мокрый снег",
"Ice pellets": "Ледяная крупа",
"Light showers of ice pellets": "Ледяная крупа",
"Moderate or heavy showers of ice pellets": "Сильная ледяная крупа",
Hail: "Град",
"Light hail": "Мелкий град",
"Moderate or heavy hail": "Крупный град",
Tornado: "Торнадо",
Hurricane: "Ураган",
Squall: "Шквал",
Dust: "Пыль",
Sand: "Песок",
"Volcanic ash": "Вулканический пепел",
"Thundery outbreaks possible": "Возможны грозы",
"Thundery outbreaks in nearby": "Грозы в округе",
};
// Функция для получения иконки погоды с умным fallback
function getWeatherIcon(conditionText) {
// Сначала проверяем русские условия
const englishCondition =
russianToEnglishWeather[conditionText] || conditionText;
// Прямой маппинг
if (weatherIcons[englishCondition]) {
return weatherIcons[englishCondition];
}
// Умный fallback на основе ключевых слов
const lowerCondition = englishCondition.toLowerCase();
if (
lowerCondition.includes("rain") ||
lowerCondition.includes("drizzle") ||
lowerCondition.includes("shower")
) {
return "cloud-rain";
}
if (
lowerCondition.includes("snow") ||
lowerCondition.includes("sleet") ||
lowerCondition.includes("hail") ||
lowerCondition.includes("ice")
) {
return "cloud-snow";
}
if (lowerCondition.includes("thunder") || lowerCondition.includes("storm")) {
return "cloud-lightning";
}
if (lowerCondition.includes("fog") || lowerCondition.includes("mist")) {
return "cloud-fog";
}
if (lowerCondition.includes("cloud") || lowerCondition.includes("overcast")) {
return "cloud";
}
if (lowerCondition.includes("sun") || lowerCondition.includes("clear")) {
return "sun";
}
if (
lowerCondition.includes("wind") ||
lowerCondition.includes("blowing") ||
lowerCondition.includes("blizzard")
) {
return "wind";
}
// Общий fallback
return "cloud";
}
// Функция для получения красивого русского названия погоды
function getBeautifulRussianName(conditionText) {
return beautifulRussianNames[conditionText] || conditionText;
}
// Функция для получения цвета иконки погоды
function getWeatherIconColor(iconName) {
const isDark =
document.documentElement.classList.contains("dark") ||
document.body.classList.contains("dark");
const colorMap = {
sun: isDark ? "sun-dark" : "sun",
moon: isDark ? "sun-dark" : "sun",
"cloud-sun": isDark ? "sun-dark" : "sun",
"cloud-moon": isDark ? "sun-dark" : "sun",
cloud: isDark ? "cloud-dark" : "cloud",
"cloud-rain": isDark ? "rain-dark" : "rain",
"cloud-drizzle": isDark ? "drizzle-dark" : "drizzle",
"cloud-snow": isDark ? "snow-dark" : "snow",
"cloud-lightning": isDark ? "thunder-dark" : "thunder",
"cloud-fog": isDark ? "fog-dark" : "fog",
wind: isDark ? "wind-dark" : "wind",
"cloud-hail": isDark ? "hail-dark" : "hail",
tornado: isDark ? "thunder-dark" : "thunder",
hurricane: isDark ? "thunder-dark" : "thunder",
};
return colorMap[iconName] || (isDark ? "cloud-dark" : "cloud");
}
// Функция для отображения текущей даты
function displayCurrentDate() {
const currentDateEl = document.getElementById("current-date");
const now = new Date();
const daysOfWeek = [
"воскресенье",
"понедельник",
"вторник",
"среда",
"четверг",
"пятница",
"суббота",
];
const months = [
"января",
"февраля",
"марта",
"апреля",
"мая",
"июня",
"июля",
"августа",
"сентября",
"октября",
"ноября",
"декабря",
];
const dayOfWeek = daysOfWeek[now.getDay()];
const day = now.getDate();
const month = months[now.getMonth()];
currentDateEl.textContent = `${dayOfWeek}, ${day} ${month}`;
}
// Функция для обновления заголовка страницы
function updatePageTitle() {
const selectedCity = citySelect.options[citySelect.selectedIndex].text;
document.title = `Погода в ${selectedCity}`;
}
// Автоматическая загрузка погоды при загрузке страницы
document.addEventListener("DOMContentLoaded", () => {
// Отправляем событие загрузки страницы
ymSendEvent('page_load');
// Отображаем текущую дату
displayCurrentDate();
// Загружаем сохраненный город из localStorage
const savedCity = localStorage.getItem("selectedCity");
if (savedCity) {
citySelect.value = savedCity;
}
// Обновляем заголовок страницы
updatePageTitle();
// Инициализируем тему
initializeTheme();
watchSystemTheme();
// Регистрируем Service Worker для PWA
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered successfully:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker registration failed:', error);
});
}
// Логи для PWA установки
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
console.log('PWA install prompt available');
deferredPrompt = e;
ymSendEvent('pwa_install_prompt');
});
window.addEventListener('appinstalled', (e) => {
console.log('PWA installed successfully');
ymSendEvent('pwa_installed');
});
// Функция для проверки реального соединения
async function checkOnlineStatus() {
try {
// Пингуем небольшой ресурс
const response = await fetch('/manifest.json', { method: 'HEAD', cache: 'no-cache' });
return response.ok;
} catch (error) {
console.log('Connection check failed:', error);
return false;
}
}
// Функция для управления плашкой оффлайн
async function updateOfflineBanner() {
const banner = document.getElementById('offline-banner');
const isOnline = await checkOnlineStatus();
if (!isOnline) {
banner.classList.remove('hidden');
console.log('Оффлайн режим активирован');
ymSendEvent('offline_mode');
} else {
banner.classList.add('hidden');
console.log('Онлайн режим восстановлен');
ymSendEvent('online_mode');
}
}
// Проверяем статус при загрузке
updateOfflineBanner();
// Слушаем изменения статуса сети и проверяем реально
window.addEventListener('online', updateOfflineBanner);
window.addEventListener('offline', updateOfflineBanner);
// Периодическая проверка каждые 30 секунд
setInterval(updateOfflineBanner, 30000);
// Сохраняем данные для графиков
window.currentHourlyData = null;
// Ждем загрузки Swiper
if (typeof Swiper !== "undefined") {
getWeatherBtn.click();
} else {
// Если Swiper еще не загрузился, ждем его
const checkSwiper = setInterval(() => {
if (typeof Swiper !== "undefined") {
clearInterval(checkSwiper);
getWeatherBtn.click();
}
}, 100);
}
});
getWeatherBtn.addEventListener("click", async () => {
const city = citySelect.value;
// Отправляем событие выбора города
ymSendEvent('city_select', { city: city });
// Сохраняем выбранный город в localStorage
localStorage.setItem("selectedCity", city);
const url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&lang=ru`;
console.log("Запрос к API:", url);
console.log("Выбранный город:", city);
try {
const response = await fetch(url);
console.log("Статус ответа:", response.status, response.statusText);
if (!response.ok) {
throw new Error(`Ошибка сети: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log("Данные получены успешно");
// Отправляем событие успешной загрузки данных
ymSendEvent('weather_load_success', { city: city });
// Отображаем текущую погоду
displayCurrentWeather(data);
// Пересоздаем иконки после обновления DOM
setTimeout(() => {
lucide.createIcons();
}, 100);
// Сохраняем данные для графиков
window.currentHourlyData = data.forecast.forecastday[0].hour;
// Таймлапс на сегодня
displayTimelapse(
data.forecast.forecastday[0].hour,
data.location.localtime
);
// Погода на неделю
displayWeeklyWeather(data.forecast.forecastday);
// Графики температуры и осадков
createTemperatureChart(data.forecast.forecastday[0].hour);
createPrecipitationChart(data.forecast.forecastday[0].hour);
// Обновляем заголовок страницы с выбранным городом
updatePageTitle();
} catch (error) {
console.error("Ошибка загрузки данных:", error);
console.error("Детали ошибки:", error.message);
if (error.response) {
console.error("Статус ответа:", error.response.status);
console.error("Текст ответа:", error.response.statusText);
}
// Отправляем событие ошибки загрузки
ymSendEvent('weather_load_error', {
city: city,
error: error.message
});
alert("Не удалось загрузить данные о погоде. Проверьте API ключ.");
}
});
function displayCurrentWeather(data) {
const current = data.current;
const iconName = getWeatherIcon(current.condition.text);
const iconColorClass = getWeatherIconColor(iconName);
const beautifulName = getBeautifulRussianName(current.condition.text);
currentTempEl.textContent = `${Math.round(current.temp_c)}°C`;
currentDescEl.innerHTML = `<i data-lucide="${iconName}" class="w-5 h-5 inline mr-2 weather-icon ${iconColorClass}"></i>${beautifulName}`;
currentHumidityEl.textContent = `${current.humidity}%`;
currentWindEl.textContent = `${current.wind_kph} км/ч`;
// Показываем блок текущей погоды
currentWeatherDiv.classList.remove("hidden");
currentWeatherDiv.classList.add("animate-fade-in");
// Пересоздаем иконки
setTimeout(() => {
lucide.createIcons();
}, 100);
}
function displayTimelapse(hourlyData, localtime) {
timelapseContainer.innerHTML = "";
const currentHour = new Date(localtime).getHours();
let currentSlideIndex = -1;
hourlyData.forEach((item, index) => {
const slide = document.createElement("div");
const hour = new Date(item.time).getHours();
const isCurrent = hour === currentHour;
if (isCurrent) currentSlideIndex = index;
slide.className = `swiper-slide timelapse-item bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-gray-700 dark:to-gray-800 p-3 rounded-xl shadow-md border ${
isCurrent
? "border-blue-500 bg-gradient-to-br from-blue-100 to-indigo-100 dark:from-blue-900/30 dark:to-indigo-900/30 ring-2 ring-blue-300 dark:ring-blue-600"
: "border-gray-100 dark:border-gray-600"
}`;
slide.style.width = "140px";
const iconName = getWeatherIcon(item.condition.text);
const iconColorClass = getWeatherIconColor(iconName);
// Отладочная информация
console.log(
`Timelapse: ${hour}:00 - Условие: "${item.condition.text}" - Иконка: "${iconName}" - Цвет: "${iconColorClass}"`
);
slide.innerHTML = `
<div class="text-center">
<p class="text-xs font-medium ${
isCurrent
? "text-blue-700 dark:text-blue-300 font-semibold"
: "text-gray-600 dark:text-gray-300"
} mb-1">${hour}:00 ${isCurrent ? "(сейчас)" : ""}</p>
<div class="flex justify-center mb-2">
<i data-lucide="${iconName}" class="w-6 h-6 weather-icon ${iconColorClass} ${
isCurrent
? "ring-2 ring-blue-300 dark:ring-blue-600 rounded-full p-1"
: ""
}"></i>
</div>
<p class="text-lg font-bold ${
isCurrent
? "text-blue-700 dark:text-blue-300"
: "text-blue-600 dark:text-blue-400"
} mb-1">${Math.round(item.temp_c)}°C</p>
<p class="text-xs ${
isCurrent
? "text-blue-600 dark:text-blue-400"
: "text-gray-600 dark:text-gray-300"
} mb-1">${getBeautifulRussianName(item.condition.text)}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">💧 ${
item.precip_mm
} мм</p>
<p class="text-xs text-gray-500 dark:text-gray-400">☔ ${
item.chance_of_rain
}%</p>
</div>
`;
timelapseContainer.appendChild(slide);
});
// Пересоздаем иконки
setTimeout(() => {
lucide.createIcons();
}, 100);
// Инициализируем Swiper если он еще не создан
setTimeout(() => {
if (!weatherSwiper) {
weatherSwiper = new Swiper(".swiper", {
slidesPerView: window.innerWidth < 768 ? 3 : 6,
spaceBetween: 12,
grabCursor: true,
freeMode: true,
mousewheel: {
enabled: true,
sensitivity: 1,
},
slidesOffsetBefore: 0,
slidesOffsetAfter: 0,
centeredSlides: false,
watchSlidesProgress: true,
});
} else {
weatherSwiper.update();
weatherSwiper.updateSlides();
}
// Фокус на текущем времени города
if (currentSlideIndex !== -1) {
setTimeout(() => {
weatherSwiper.slideTo(currentSlideIndex, 800);
}, 200);
}
}, 50);
}
function displayWeeklyWeather(forecastData) {
const weeklyContainer = document.getElementById("weekly-container");
weeklyContainer.innerHTML = "";
const daysOfWeek = ["вс", "пн", "вт", "ср", "чт", "пт", "сб"];
forecastData.forEach((day, index) => {
const date = new Date(day.date);
const dayOfWeek = daysOfWeek[date.getDay()];
const dayNumber = date.getDate();
const month = date.toLocaleDateString("ru-RU", { month: "short" });
const iconName = getWeatherIcon(day.day.condition.text);
const iconColorClass = getWeatherIconColor(iconName);
const isToday = index === 0;
// Отладочная информация
console.log(
`Weekly: ${isToday ? "Сегодня" : dayOfWeek} - Условие: "${
day.day.condition.text
}" - Иконка: "${iconName}" - Цвет: "${iconColorClass}"`
);
const card = document.createElement("div");
card.className = `text-center p-4 rounded-xl border transition-all duration-200 hover:shadow-lg ${
isToday
? "bg-blue-50 dark:bg-blue-900/20 border-blue-300 dark:border-blue-700 ring-2 ring-blue-200 dark:ring-blue-600"
: "bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-300 dark:hover:border-blue-600"
}`;
card.innerHTML = `
<div class="mb-2">
<p class="text-sm font-medium text-gray-600 dark:text-gray-300 ${
isToday ? "text-blue-700 dark:text-blue-300" : ""
}">
${isToday ? "Сегодня" : dayOfWeek}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">${dayNumber} ${month}</p>
</div>
<div class="flex justify-center mb-3">
<i data-lucide="${iconName}" class="w-8 h-8 weather-icon ${iconColorClass} ${
isToday ? "ring-2 ring-blue-300 dark:ring-blue-600 rounded-full p-1" : ""
}"></i>
</div>
<div class="mb-2">
<p class="text-lg font-bold ${
isToday
? "text-blue-700 dark:text-blue-300"
: "text-gray-800 dark:text-gray-200"
}">
${Math.round(day.day.maxtemp_c)}°
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">
${Math.round(day.day.mintemp_c)}°
</p>
</div>
<p class="text-xs ${
isToday
? "text-blue-600 dark:text-blue-400"
: "text-gray-600 dark:text-gray-300"
}">
${getBeautifulRussianName(day.day.condition.text)}
</p>
`;
weeklyContainer.appendChild(card);
});
// Пересоздаем иконки для недельного прогноза
setTimeout(() => {
lucide.createIcons();
}, 100);
}
// Функция создания графика температуры
function createTemperatureChart(hourlyData) {
const ctx = document.getElementById("temperatureChart").getContext("2d");
const colors = getThemeColors();
const isDark =
document.documentElement.classList.contains("dark") ||
document.body.classList.contains("dark");
// Отправляем событие просмотра графика температуры
ymSendEvent('chart_view', { chart_type: 'temperature' });
// Подготавливаем данные
const labels = hourlyData.map((item) => {
const hour = new Date(item.time).getHours();
return `${hour}:00`;
});
const temperatures = hourlyData.map((item) => Math.round(item.temp_c));
// Уничтожаем предыдущий график если он существует
if (temperatureChart) {
temperatureChart.destroy();
}
// Создаем новый график
temperatureChart = new Chart(ctx, {
type: "line",
data: {
labels: labels,
datasets: [
{
label: "Температура (°C)",
data: temperatures,
borderColor: "#3b82f6",
backgroundColor: "rgba(59, 130, 246, 0.1)",
borderWidth: 3,
fill: true,
tension: 0.4,
pointBackgroundColor: "#3b82f6",
pointBorderColor: "#ffffff",
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
pointHoverBackgroundColor: "#2563eb",
pointHoverBorderColor: "#ffffff",
pointHoverBorderWidth: 2,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
tooltip: {
backgroundColor: colors.backgroundColor,
titleColor: colors.titleColor,
bodyColor: colors.bodyColor,
cornerRadius: 8,
displayColors: false,
callbacks: {
title: function (context) {
return `Время: ${context[0].label}`;
},
label: function (context) {
return `Температура: ${context.parsed.y}°C`;
},
},
},
},
scales: {
x: {
grid: {
display: false,
},
ticks: {
color: colors.textColor,
font: {
size: 12,
color: colors.textColor,
},
},
},
y: {
grid: {
color: colors.gridColor,
lineWidth: 1,
},
ticks: {
color: colors.textColor,
font: {
size: 12,
color: colors.textColor,
},
callback: function (value) {
return value + "°C";
},
},
},
},
animation: {
duration: 2000,
easing: "easeInOutQuart",
},
interaction: {
intersect: false,
mode: "index",
},
},
});
}
// Функция для получения цветов в зависимости от темы
function getThemeColors() {
const isDark =
document.documentElement.classList.contains("dark") ||
document.body.classList.contains("dark");
return {
textColor: isDark ? "#d1d5db" : "#ffffff",
gridColor: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)",
backgroundColor: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.8)",
titleColor: isDark ? "#ffffff" : "#ffffff",
bodyColor: isDark ? "#ffffff" : "#ffffff",
};
}
// Функции для работы с темами
function initializeTheme() {
const savedTheme = localStorage.getItem("theme") || "auto";
setTheme(savedTheme);
updateThemeButtons(savedTheme);
// Если выбрана авто-тема, проверяем системную тему при загрузке
if (savedTheme === "auto") {
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
updateThemeButtons(prefersDark ? "dark" : "light");
}
// Добавляем обработчики событий для кнопок темы
if (themeButtons.light) {
themeButtons.light.addEventListener("click", () => {
setTheme("light");
updateThemeButtons("light");
});
}
if (themeButtons.auto) {
themeButtons.auto.addEventListener("click", () => {
setTheme("auto");
updateThemeButtons("auto");
});
}
if (themeButtons.dark) {
themeButtons.dark.addEventListener("click", () => {
setTheme("dark");
updateThemeButtons("dark");
});
}
}
function setTheme(theme) {
const html = document.documentElement;
// Удаляем предыдущие настройки темы
html.removeAttribute("data-theme");
html.classList.remove("dark");
document.body.classList.remove("dark");
if (theme === "dark") {
html.setAttribute("data-theme", "dark");
html.classList.add("dark");
document.body.classList.add("dark");
} else if (theme === "auto") {
// Определяем системную тему
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
if (prefersDark) {
html.setAttribute("data-theme", "dark");
html.classList.add("dark");
document.body.classList.add("dark");
} else {
html.setAttribute("data-theme", "light");
}
} else {
// Светлая тема
html.setAttribute("data-theme", "light");
}
// Отправляем событие смены темы
ymSendEvent('theme_change', { theme: theme });
// Сохраняем выбранную тему
localStorage.setItem("theme", theme);
// Обновляем иконки после смены темы
setTimeout(() => {
if (typeof lucide !== "undefined") {
lucide.createIcons();
}
}, 100);
// Принудительно перестраиваем Swiper если он существует
if (window.weatherSwiper) {
setTimeout(() => {
window.weatherSwiper.update();
window.weatherSwiper.updateSlides();
}, 150);
}
// Обновляем графики если они существуют
if (temperatureChart) {
temperatureChart.destroy();
createTemperatureChart(window.currentHourlyData || []);
}
if (precipitationChart) {
precipitationChart.destroy();
createPrecipitationChart(window.currentHourlyData || []);
}
}
function updateThemeButtons(activeTheme) {
Object.keys(themeButtons).forEach((theme) => {
if (themeButtons[theme]) {
if (theme === activeTheme) {
themeButtons[theme].classList.add("active");
} else {
themeButtons[theme].classList.remove("active");
}
}
});
}
// Слушаем изменения системной темы
function watchSystemTheme() {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", (e) => {
const currentTheme = localStorage.getItem("theme");
if (currentTheme === "auto") {
setTheme("auto");
}
});
}
// Функция создания графика осадков
function createPrecipitationChart(hourlyData) {
const ctx = document.getElementById("precipitationChart").getContext("2d");
const colors = getThemeColors();
// Отправляем событие просмотра графика осадков
ymSendEvent('chart_view', { chart_type: 'precipitation' });
// Подготавливаем данные
const labels = hourlyData.map((item) => {
const hour = new Date(item.time).getHours();
return `${hour}:00`;
});
const precipitations = hourlyData.map((item) => item.precip_mm);
const chances = hourlyData.map((item) => item.chance_of_rain);
// Уничтожаем предыдущий график если он существует
if (precipitationChart) {
precipitationChart.destroy();
}
// Создаем новый график
precipitationChart = new Chart(ctx, {
type: "bar",
data: {
labels: labels,
datasets: [
{
label: "Осадки (мм)",
data: precipitations,
backgroundColor: "rgba(59, 130, 246, 0.6)",
borderColor: "#3b82f6",
borderWidth: 1,
borderRadius: 4,
borderSkipped: false,
barThickness: "flex",
maxBarThickness: 30,
},
{
label: "Вероятность (%)",
data: chances,
type: "line",
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
borderWidth: 2,
fill: false,
tension: 0.4,
pointBackgroundColor: "#ef4444",
pointBorderColor: "#ffffff",
pointBorderWidth: 2,
pointRadius: 3,
pointHoverRadius: 5,
pointHoverBackgroundColor: "#dc2626",
pointHoverBorderColor: "#ffffff",
pointHoverBorderWidth: 2,
yAxisID: "y1",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
tooltip: {
backgroundColor: colors.backgroundColor,
titleColor: colors.titleColor,
bodyColor: colors.bodyColor,
cornerRadius: 8,
displayColors: true,
callbacks: {
title: function (context) {
return `Время: ${context[0].label}`;
},
label: function (context) {
if (context.datasetIndex === 0) {
return `Осадки: ${context.parsed.y} мм`;
} else {
return `Вероятность: ${context.parsed.y}%`;
}
},
},
},
},
scales: {
x: {
grid: {
display: false,
},
ticks: {
color: colors.textColor,
font: {
size: 12,
color: colors.textColor,
},
},
},
y: {
type: "linear",
display: true,
position: "left",
grid: {
color: colors.gridColor,
lineWidth: 1,
},
ticks: {
color: colors.textColor,
font: {
size: 12,
color: colors.textColor,
},
callback: function (value) {
return value + " мм";
},
},
title: {
display: true,
text: "Осадки (мм)",
color: "#3b82f6",
font: {
size: 14,
weight: "bold",
},
},
},
y1: {
type: "linear",
display: true,
position: "right",
grid: {
drawOnChartArea: false,
},
ticks: {
color: "#ef4444",
font: {
size: 12,
color: "#ef4444",
},
callback: function (value) {
return value + "%";
},
},
title: {
display: true,
text: "Вероятность (%)",
color: "#ef4444",
font: {
size: 14,
weight: "bold",
},
},
},
},
animation: {
duration: 2000,
easing: "easeInOutQuart",
delay: function (context) {
return context.dataIndex * 100;
},
},
interaction: {
intersect: false,
mode: "index",
},
},
});
}