Добавлены новые функции
This commit is contained in:
parent
d484ccf9a9
commit
38ee07e881
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
apikey.txt
|
||||
Идеи.txt
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -13,7 +13,7 @@
|
||||
- **Недельный прогноз**: погода на 7 дней вперед
|
||||
- **Интерактивные графики**: температура и осадки с помощью Chart.js
|
||||
- **Адаптивный дизайн**: корректное отображение на всех устройствах
|
||||
- **Выбор городов**: предустановленный список российских городов
|
||||
- **Выбор городов**: поиск городов через API WeatherAPI.com
|
||||
- **Система тем**: светлая, темная и автоматическая (по системным настройкам)
|
||||
- **Анимации**: плавные переходы и hover-эффекты
|
||||
- **PWA (Progressive Web App)**: установка как нативное приложение, оффлайн режим с кэшированием данных
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
### Backend
|
||||
|
||||
- **Node.js** v8+ (устаревшая версия, рекомендуется обновить до LTS)
|
||||
- **Node.js** v8 (устаревшая версия, рекомендуется обновить до LTS 18+)
|
||||
- **Express.js** v4.17.1 - веб-сервер
|
||||
- **Service Worker** - кэширование и оффлайн поддержка
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
|
||||
### Системные требования
|
||||
|
||||
- **Node.js** v8+ (⚠️ устаревшая версия, рекомендуется использовать LTS версию Node.js 18+)
|
||||
- **Node.js** v8 (⚠️ устаревшая версия, рекомендуется использовать актуальную LTS версию Node.js 18+)
|
||||
- **npm** для управления пакетами
|
||||
|
||||
### Установка
|
||||
@ -157,6 +157,7 @@
|
||||
|
||||
- **WeatherAPI.com** - источник данных о погоде
|
||||
- **Google Fonts API** - загрузка шрифтов Inter
|
||||
- **Yandex Metrika** - аналитика и отслеживание событий
|
||||
- **CDN сервисы** для библиотек JavaScript (jsDelivr, unpkg)
|
||||
|
||||
## Разработчик
|
||||
@ -179,7 +180,7 @@
|
||||
|
||||
1. Зарегистрироваться на [WeatherAPI.com](https://weatherapi.com)
|
||||
2. Получить бесплатный API ключ
|
||||
3. Добавить ключ в переменные окружения или конфигурацию сервера(server.js - const API_KEY = "API_KEY_HERE";)
|
||||
3. Добавить ключ в script.js (const API_KEY = "API_KEY_HERE";)
|
||||
|
||||
### Производительность
|
||||
|
||||
|
||||
279
index.html
279
index.html
@ -318,27 +318,28 @@
|
||||
>
|
||||
<i data-lucide="map-pin" class="w-5 h-5 text-blue-500"></i>
|
||||
<label
|
||||
for="city"
|
||||
for="city-search"
|
||||
class="font-medium text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>Выберите город:</label
|
||||
>
|
||||
</div>
|
||||
<select
|
||||
<input
|
||||
id="city"
|
||||
class="flex-1 px-6 py-3 border-2 border-gray-200 rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 bg-white text-gray-700 font-medium min-w-[200px]"
|
||||
type="text"
|
||||
list="city-list"
|
||||
placeholder="Введите название города"
|
||||
class="flex-1 px-6 py-3 border-2 border-gray-200 dark:border-gray-600 rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 font-medium min-w-[200px]"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<datalist id="city-list"></datalist>
|
||||
<button
|
||||
id="geolocation-btn"
|
||||
class="px-4 py-3 bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 font-semibold rounded-xl transition-all duration-200 transform hover:scale-105 flex items-center gap-2"
|
||||
title="Определить мое местоположение"
|
||||
>
|
||||
<option value="Moscow">Москва</option>
|
||||
<option value="Saint Petersburg">Санкт-Петербург</option>
|
||||
<option value="Novosibirsk">Новосибирск</option>
|
||||
<option value="Yekaterinburg">Екатеринбург</option>
|
||||
<option value="Nizhny Novgorod">Нижний Новгород</option>
|
||||
<option value="Kazan">Казань</option>
|
||||
<option value="Chelyabinsk">Челябинск</option>
|
||||
<option value="Omsk">Омск</option>
|
||||
<option value="Samara">Самара</option>
|
||||
<option value="Rostov-on-Don">Ростов-на-Дону</option>
|
||||
</select>
|
||||
<i data-lucide="navigation" class="w-5 h-5 text-black dark:text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
id="get-weather"
|
||||
class="px-8 py-3 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-xl transition-all duration-200 transform hover:scale-105 shadow-lg hover:shadow-xl flex items-center gap-2"
|
||||
@ -364,8 +365,8 @@
|
||||
<i data-lucide="thermometer" class="w-6 h-6 text-blue-500"></i>
|
||||
Текущая погода
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="text-center">
|
||||
<!-- Температура отдельно в первом ряду -->
|
||||
<div class="text-center mb-6">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i
|
||||
data-lucide="thermometer"
|
||||
@ -375,6 +376,13 @@
|
||||
--°C
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
class="text-sm"
|
||||
id="current-feels-like"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Ощущается как --°C
|
||||
</p>
|
||||
<p
|
||||
class="text-lg"
|
||||
id="current-desc"
|
||||
@ -383,6 +391,9 @@
|
||||
Загрузка...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Влажность, ветер и видимость во втором ряду -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="droplets" class="w-5 h-5 text-blue-500"></i>
|
||||
@ -420,6 +431,98 @@
|
||||
>
|
||||
-- км/ч
|
||||
</p>
|
||||
<p
|
||||
class="text-sm"
|
||||
id="current-gust"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Порывы: -- км/ч
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="eye" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Видимость
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="current-vis"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
-- км
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6">
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="gauge" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Давление
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="current-pressure"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
-- гПа
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="sun" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
UV-индекс
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="current-uv"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
--
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="compass" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Направление ветра
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<i data-lucide="arrow-up" id="wind-direction-icon" class="w-6 h-6 text-blue-500 transform"></i>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="current-wind-dir"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
--
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -428,7 +531,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Timelapse Section -->
|
||||
<div class="timelapse-weather weather-card rounded-2xl shadow-xl p-8">
|
||||
<div class="timelapse-weather weather-card rounded-2xl shadow-xl p-8 mb-8">
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
@ -442,7 +545,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Weekly Weather Section -->
|
||||
<div class="weekly-weather weather-card rounded-2xl shadow-xl p-8 mt-8">
|
||||
<div class="weekly-weather weather-card rounded-2xl shadow-xl p-8 mb-8">
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
@ -458,6 +561,146 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Astronomy Section -->
|
||||
<div
|
||||
id="astronomy-section"
|
||||
class="hidden weather-card rounded-2xl shadow-xl p-8 mb-8"
|
||||
>
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center justify-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
<i data-lucide="sunrise" class="w-6 h-6 text-blue-500"></i>
|
||||
Астрономическая информация
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="sunrise" class="w-5 h-5 text-orange-500"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Восход солнца
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="sunrise-time"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
--
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="sunset" class="w-5 h-5 text-orange-600"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Заход солнца
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="sunset-time"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
--
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="moon" class="w-5 h-5 text-blue-400"></i>
|
||||
<div>
|
||||
<p
|
||||
class="text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>
|
||||
Фаза луны
|
||||
</p>
|
||||
<p
|
||||
class="text-2xl font-semibold"
|
||||
id="moon-phase"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
--
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Air Quality Section -->
|
||||
<div
|
||||
id="air-quality-section"
|
||||
class="hidden weather-card rounded-2xl shadow-xl p-8 mb-8"
|
||||
>
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center justify-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
>
|
||||
<i data-lucide="wind" class="w-6 h-6 text-blue-500"></i>
|
||||
Качество воздуха
|
||||
</h2>
|
||||
<div class="text-center mb-6">
|
||||
<div class="flex items-center justify-center gap-4 mb-4">
|
||||
<div class="text-6xl font-bold" id="aqi-value">--</div>
|
||||
<div class="text-left">
|
||||
<div class="text-lg font-semibold" id="aqi-status">Загрузка...</div>
|
||||
<div class="text-sm" style="color: var(--theme-text-secondary)" id="aqi-description">Индекс качества воздуха</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4 mb-4">
|
||||
<div class="h-4 rounded-full transition-all duration-500" id="aqi-bar"></div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row justify-center gap-8 mt-6">
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="activity" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p class="text-sm" style="color: var(--theme-text-secondary)">Мелкие частицы</p>
|
||||
<p class="text-lg font-semibold" id="pm25" style="color: var(--theme-text)">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="circle-dot" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p class="text-sm" style="color: var(--theme-text-secondary)">Крупные частицы</p>
|
||||
<p class="text-lg font-semibold" id="pm10" style="color: var(--theme-text)">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="zap" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p class="text-sm" style="color: var(--theme-text-secondary)">Диоксид азота</p>
|
||||
<p class="text-lg font-semibold" id="no2" style="color: var(--theme-text)">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="flex items-center justify-center gap-2 mb-2">
|
||||
<i data-lucide="sun" class="w-5 h-5 text-blue-500"></i>
|
||||
<div>
|
||||
<p class="text-sm" style="color: var(--theme-text-secondary)">Озон</p>
|
||||
<p class="text-lg font-semibold" id="o3" style="color: var(--theme-text)">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="charts-section weather-card rounded-2xl shadow-xl p-8 mt-8">
|
||||
<h2
|
||||
|
||||
600
script.js
600
script.js
@ -6,8 +6,8 @@ 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);
|
||||
if (typeof ym !== "undefined" && YM_COUNTER_ID !== "YOUR_COUNTER_ID") {
|
||||
ym(YM_COUNTER_ID, "reachGoal", eventName, params);
|
||||
console.log(`Yandex Metrika event: ${eventName}`, params);
|
||||
}
|
||||
}
|
||||
@ -19,8 +19,11 @@ const themeButtons = {
|
||||
dark: document.getElementById("theme-dark"),
|
||||
};
|
||||
|
||||
const citySelect = document.getElementById("city");
|
||||
const cityInput = document.getElementById("city");
|
||||
const cityList = document.getElementById("city-list");
|
||||
let searchTimeout = null;
|
||||
const getWeatherBtn = document.getElementById("get-weather");
|
||||
const geolocationBtn = document.getElementById("geolocation-btn");
|
||||
const timelapseContainer = document.getElementById("timelapse-container");
|
||||
const currentWeatherDiv = document.getElementById("current-weather");
|
||||
const currentTempEl = document.getElementById("current-temp");
|
||||
@ -28,6 +31,10 @@ const currentDescEl = document.getElementById("current-desc");
|
||||
const currentHumidityEl = document.getElementById("current-humidity");
|
||||
const currentWindEl = document.getElementById("current-wind");
|
||||
|
||||
// Глобальные переменные для координат пользователя (для fallback)
|
||||
let userLatitude = null;
|
||||
let userLongitude = null;
|
||||
|
||||
// Глобальная переменная для Swiper
|
||||
let weatherSwiper;
|
||||
|
||||
@ -35,6 +42,73 @@ let weatherSwiper;
|
||||
let temperatureChart = null;
|
||||
let precipitationChart = null;
|
||||
|
||||
// Функция поиска городов через API
|
||||
async function searchCities(query) {
|
||||
if (!query || query.length < 3) {
|
||||
cityList.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `https://api.weatherapi.com/v1/search.json?key=${API_KEY}&q=${encodeURIComponent(
|
||||
query
|
||||
)}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const cities = await response.json();
|
||||
|
||||
// Ограничить до 10 результатов
|
||||
const limitedCities = cities.slice(0, 10);
|
||||
|
||||
// Заполнить datalist
|
||||
cityList.innerHTML = limitedCities
|
||||
.map((city) => {
|
||||
const displayName = `${city.name}, ${city.region}, ${city.country}`;
|
||||
return `<option value="${displayName}">`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
console.log(`Найдено ${limitedCities.length} городов для "${query}"`);
|
||||
} catch (error) {
|
||||
console.error("Ошибка поиска городов:", error);
|
||||
cityList.innerHTML = "";
|
||||
// Можно добавить уведомление пользователю
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик ввода для поиска
|
||||
function setupCitySearch() {
|
||||
cityInput.addEventListener("input", (e) => {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
// Очистить предыдущий таймаут
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Установить новый таймаут для дебаунсинга (500ms)
|
||||
searchTimeout = setTimeout(() => {
|
||||
searchCities(query);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Обработчик выбора города из списка
|
||||
cityInput.addEventListener("change", () => {
|
||||
const selectedCity = cityInput.value.trim();
|
||||
if (selectedCity) {
|
||||
// Сохранить выбранный город
|
||||
localStorage.setItem("selectedCity", selectedCity);
|
||||
// Обновить заголовок страницы
|
||||
updatePageTitle();
|
||||
console.log("Город выбран:", selectedCity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Иконки погоды
|
||||
const weatherIcons = {
|
||||
Sunny: "sun",
|
||||
@ -235,6 +309,24 @@ const beautifulRussianNames = {
|
||||
"Thundery outbreaks in nearby": "Грозы в округе",
|
||||
};
|
||||
|
||||
// Функция для конвертации времени в 24-часовой формат
|
||||
function convertTo24Hour(timeStr) {
|
||||
if (!timeStr.includes(" ")) {
|
||||
// Уже в 24-часовом формате или без AM/PM
|
||||
return timeStr;
|
||||
}
|
||||
const [time, period] = timeStr.split(" ");
|
||||
let [hours, minutes] = time.split(":").map(Number);
|
||||
if (period === "PM" && hours !== 12) {
|
||||
hours += 12;
|
||||
} else if (period === "AM" && hours === 12) {
|
||||
hours = 0;
|
||||
}
|
||||
return `${hours.toString().padStart(2, "0")}:${minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
// Функция для получения иконки погоды с умным fallback
|
||||
function getWeatherIcon(conditionText) {
|
||||
// Сначала проверяем русские условия
|
||||
@ -318,6 +410,149 @@ function getWeatherIconColor(iconName) {
|
||||
return colorMap[iconName] || (isDark ? "cloud-dark" : "cloud");
|
||||
}
|
||||
|
||||
// Функция для перевода направления ветра на русский
|
||||
function getRussianWindDirection(windDir) {
|
||||
const windDirMap = {
|
||||
N: "Север",
|
||||
NNE: "Северо-северо-восток",
|
||||
NE: "Северо-восток",
|
||||
ENE: "Востоко-северо-восток",
|
||||
E: "Восток",
|
||||
ESE: "Востоко-юго-восток",
|
||||
SE: "Юго-восток",
|
||||
SSE: "Юго-юго-восток",
|
||||
S: "Юг",
|
||||
SSW: "Юго-юго-запад",
|
||||
SW: "Юго-запад",
|
||||
WSW: "Западо-юго-запад",
|
||||
W: "Запад",
|
||||
WNW: "Западо-северо-запад",
|
||||
NW: "Северо-запад",
|
||||
NNW: "Северо-северо-запад",
|
||||
};
|
||||
|
||||
return windDirMap[windDir] || windDir; // Если направление не найдено, вернуть оригинал
|
||||
}
|
||||
|
||||
// Функция для геолокации
|
||||
async function getCurrentLocation() {
|
||||
console.log("Начало функции getCurrentLocation");
|
||||
if (!navigator.geolocation) {
|
||||
console.error("Геолокация не поддерживается браузером");
|
||||
alert("Геолокация не поддерживается вашим браузером");
|
||||
ymSendEvent("geolocation_error", { error: "not_supported" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем классы иконки на основе текущей темы
|
||||
const isDark =
|
||||
document.documentElement.classList.contains("dark") ||
|
||||
document.body.classList.contains("dark");
|
||||
const iconClasses = `w-5 h-5 ${isDark ? "text-white" : "text-black"}`;
|
||||
console.log("Классы иконки для текущей темы:", iconClasses);
|
||||
console.log("Текущий innerHTML кнопки:", geolocationBtn.innerHTML);
|
||||
|
||||
geolocationBtn.disabled = true;
|
||||
geolocationBtn.innerHTML =
|
||||
'<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>';
|
||||
|
||||
console.log("Вызов navigator.geolocation.getCurrentPosition");
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
async (position) => {
|
||||
const { latitude, longitude } = position.coords;
|
||||
console.log("Получены координаты:", { latitude, longitude });
|
||||
ymSendEvent("geolocation_success");
|
||||
|
||||
// Сохраняем координаты для возможного fallback
|
||||
userLatitude = latitude;
|
||||
userLongitude = longitude;
|
||||
|
||||
try {
|
||||
// Получаем ближайшие города по координатам через WeatherAPI search endpoint
|
||||
const locationUrl = `https://api.weatherapi.com/v1/search.json?key=${API_KEY}&q=${latitude},${longitude}`;
|
||||
console.log("Запрос ближайших городов к WeatherAPI:", locationUrl);
|
||||
const response = await fetch(locationUrl);
|
||||
console.log("Статус ответа:", response.status);
|
||||
const cities = await response.json();
|
||||
console.log("Найденные города:", cities);
|
||||
|
||||
if (cities.length > 0) {
|
||||
const cityName = cities[0].name;
|
||||
console.log("Определенный город:", cityName);
|
||||
|
||||
// Установить город в input поле
|
||||
cityInput.value = cityName;
|
||||
console.log(
|
||||
"Установленное значение cityInput.value:",
|
||||
cityInput.value
|
||||
);
|
||||
localStorage.setItem("selectedCity", cityName);
|
||||
console.log("Сохранено в localStorage: selectedCity =", cityName);
|
||||
updatePageTitle();
|
||||
|
||||
// Автоматически загружаем погоду
|
||||
console.log("Автоматический клик на кнопку погоды");
|
||||
getWeatherBtn.click();
|
||||
} else {
|
||||
console.error("Не найдено городов по координатам");
|
||||
alert(
|
||||
"Не удалось определить ближайший город по вашему местоположению"
|
||||
);
|
||||
ymSendEvent("geolocation_city_error", { error: "no_cities_found" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Ошибка получения города по координатам:", error);
|
||||
alert("Не удалось определить город по вашему местоположению");
|
||||
ymSendEvent("geolocation_city_error", { error: error.message });
|
||||
}
|
||||
|
||||
geolocationBtn.disabled = false;
|
||||
geolocationBtn.innerHTML = `<i data-lucide="navigation" class="${iconClasses}"></i>`;
|
||||
console.log(
|
||||
"Восстановленный innerHTML кнопки после ошибки:",
|
||||
geolocationBtn.innerHTML
|
||||
);
|
||||
lucide.createIcons();
|
||||
},
|
||||
(error) => {
|
||||
console.error("Ошибка геолокации:", error);
|
||||
let errorMessage = "Не удалось получить ваше местоположение";
|
||||
|
||||
switch (error.code) {
|
||||
case error.PERMISSION_DENIED:
|
||||
errorMessage =
|
||||
"Доступ к геолокации запрещен. Разрешите доступ в настройках браузера.";
|
||||
break;
|
||||
case error.POSITION_UNAVAILABLE:
|
||||
errorMessage = "Информация о местоположении недоступна.";
|
||||
break;
|
||||
case error.TIMEOUT:
|
||||
errorMessage = "Превышено время ожидания геолокации.";
|
||||
break;
|
||||
}
|
||||
|
||||
alert(errorMessage);
|
||||
ymSendEvent("geolocation_error", {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
});
|
||||
|
||||
geolocationBtn.disabled = false;
|
||||
geolocationBtn.innerHTML = `<i data-lucide="navigation" class="${iconClasses}"></i>`;
|
||||
console.log(
|
||||
"Восстановленный innerHTML кнопки:",
|
||||
geolocationBtn.innerHTML
|
||||
);
|
||||
lucide.createIcons();
|
||||
},
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 300000, // 5 минут
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Функция для отображения текущей даты
|
||||
function displayCurrentDate() {
|
||||
const currentDateEl = document.getElementById("current-date");
|
||||
@ -356,14 +591,16 @@ function displayCurrentDate() {
|
||||
|
||||
// Функция для обновления заголовка страницы
|
||||
function updatePageTitle() {
|
||||
const selectedCity = citySelect.options[citySelect.selectedIndex].text;
|
||||
document.title = `Погода в ${selectedCity}`;
|
||||
const selectedCity = cityInput.value;
|
||||
// Извлечь только название города из "Город, Регион, Страна"
|
||||
const cityName = selectedCity.split(",")[0].trim();
|
||||
document.title = `Погода в ${cityName || "городе"}`;
|
||||
}
|
||||
|
||||
// Автоматическая загрузка погоды при загрузке страницы
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Отправляем событие загрузки страницы
|
||||
ymSendEvent('page_load');
|
||||
ymSendEvent("page_load");
|
||||
|
||||
// Отображаем текущую дату
|
||||
displayCurrentDate();
|
||||
@ -371,64 +608,77 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// Загружаем сохраненный город из localStorage
|
||||
const savedCity = localStorage.getItem("selectedCity");
|
||||
if (savedCity) {
|
||||
citySelect.value = savedCity;
|
||||
cityInput.value = savedCity;
|
||||
}
|
||||
|
||||
// Настраиваем поиск городов
|
||||
setupCitySearch();
|
||||
|
||||
// Обновляем заголовок страницы
|
||||
updatePageTitle();
|
||||
|
||||
// Обработчик для геолокации
|
||||
geolocationBtn.addEventListener("click", getCurrentLocation);
|
||||
|
||||
// Инициализируем тему
|
||||
initializeTheme();
|
||||
watchSystemTheme();
|
||||
|
||||
// Регистрируем Service Worker для PWA
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/service-worker.js")
|
||||
.then(function (registration) {
|
||||
console.log('Service Worker registered successfully:', registration.scope);
|
||||
console.log(
|
||||
"Service Worker registered successfully:",
|
||||
registration.scope
|
||||
);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('Service Worker registration failed:', error);
|
||||
console.log("Service Worker registration failed:", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Логи для PWA установки
|
||||
let deferredPrompt;
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('PWA install prompt available');
|
||||
window.addEventListener("beforeinstallprompt", (e) => {
|
||||
console.log("PWA install prompt available");
|
||||
deferredPrompt = e;
|
||||
ymSendEvent('pwa_install_prompt');
|
||||
ymSendEvent("pwa_install_prompt");
|
||||
});
|
||||
|
||||
window.addEventListener('appinstalled', (e) => {
|
||||
console.log('PWA installed successfully');
|
||||
ymSendEvent('pwa_installed');
|
||||
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' });
|
||||
const response = await fetch("/manifest.json", {
|
||||
method: "HEAD",
|
||||
cache: "no-cache",
|
||||
});
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.log('Connection check failed:', error);
|
||||
console.log("Connection check failed:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для управления плашкой оффлайн
|
||||
async function updateOfflineBanner() {
|
||||
const banner = document.getElementById('offline-banner');
|
||||
const banner = document.getElementById("offline-banner");
|
||||
const isOnline = await checkOnlineStatus();
|
||||
if (!isOnline) {
|
||||
banner.classList.remove('hidden');
|
||||
console.log('Оффлайн режим активирован');
|
||||
ymSendEvent('offline_mode');
|
||||
banner.classList.remove("hidden");
|
||||
console.log("Оффлайн режим активирован");
|
||||
ymSendEvent("offline_mode");
|
||||
} else {
|
||||
banner.classList.add('hidden');
|
||||
console.log('Онлайн режим восстановлен');
|
||||
ymSendEvent('online_mode');
|
||||
banner.classList.add("hidden");
|
||||
console.log("Онлайн режим восстановлен");
|
||||
ymSendEvent("online_mode");
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,8 +686,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
updateOfflineBanner();
|
||||
|
||||
// Слушаем изменения статуса сети и проверяем реально
|
||||
window.addEventListener('online', updateOfflineBanner);
|
||||
window.addEventListener('offline', updateOfflineBanner);
|
||||
window.addEventListener("online", updateOfflineBanner);
|
||||
window.addEventListener("offline", updateOfflineBanner);
|
||||
|
||||
// Периодическая проверка каждые 30 секунд
|
||||
setInterval(updateOfflineBanner, 30000);
|
||||
@ -460,30 +710,54 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
getWeatherBtn.addEventListener("click", async () => {
|
||||
const city = citySelect.value;
|
||||
const cityFull = cityInput.value.trim();
|
||||
if (!cityFull) {
|
||||
alert("Пожалуйста, введите название города");
|
||||
return;
|
||||
}
|
||||
|
||||
// Извлечь только название города для API
|
||||
const city = cityFull.split(",")[0].trim();
|
||||
|
||||
// Отправляем событие выбора города
|
||||
ymSendEvent('city_select', { city: city });
|
||||
ymSendEvent("city_select", { city: city, cityFull: cityFull });
|
||||
|
||||
// Сохраняем выбранный город в localStorage
|
||||
localStorage.setItem("selectedCity", city);
|
||||
// Сохраняем выбранный город в localStorage (полное название для отображения)
|
||||
localStorage.setItem("selectedCity", cityFull);
|
||||
|
||||
const url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&lang=ru`;
|
||||
let url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&aqi=yes&lang=ru`;
|
||||
|
||||
console.log("Запрос к API:", url);
|
||||
console.log("Выбранный город:", city);
|
||||
console.log("API ключ используется:", API_KEY);
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
let response = await fetch(url);
|
||||
console.log("Статус ответа:", response.status, response.statusText);
|
||||
console.log(
|
||||
"Заголовки ответа:",
|
||||
Object.fromEntries(response.headers.entries())
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Ошибка сети: ${response.status} ${response.statusText}`);
|
||||
let errorText = "";
|
||||
try {
|
||||
errorText = await response.text();
|
||||
console.log("Текст ошибки от API:", errorText);
|
||||
} catch (e) {
|
||||
console.log("Не удалось прочитать текст ошибки");
|
||||
}
|
||||
throw new Error(
|
||||
`Ошибка сети: ${response.status} ${response.statusText}${
|
||||
errorText ? ` - ${errorText}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("Данные получены успешно");
|
||||
|
||||
// Отправляем событие успешной загрузки данных
|
||||
ymSendEvent('weather_load_success', { city: city });
|
||||
ymSendEvent("weather_load_success", { city: city });
|
||||
|
||||
// Отображаем текущую погоду
|
||||
displayCurrentWeather(data);
|
||||
@ -509,6 +783,12 @@ getWeatherBtn.addEventListener("click", async () => {
|
||||
createTemperatureChart(data.forecast.forecastday[0].hour);
|
||||
createPrecipitationChart(data.forecast.forecastday[0].hour);
|
||||
|
||||
// Астрономическая информация
|
||||
displayAstronomy(data.forecast.forecastday[0].astro);
|
||||
|
||||
// Качество воздуха
|
||||
displayAirQuality(data.current.air_quality);
|
||||
|
||||
// Обновляем заголовок страницы с выбранным городом
|
||||
updatePageTitle();
|
||||
} catch (error) {
|
||||
@ -519,16 +799,230 @@ getWeatherBtn.addEventListener("click", async () => {
|
||||
console.error("Текст ответа:", error.response.statusText);
|
||||
}
|
||||
|
||||
// Попытка загрузить из кэша для оффлайн режима
|
||||
try {
|
||||
const cache = await caches.open(API_CACHE_NAME);
|
||||
const cachedResponse = await cache.match(url);
|
||||
if (cachedResponse) {
|
||||
console.log("Используем кэшированные данные погоды для оффлайн режима");
|
||||
const cachedData = await cachedResponse.json();
|
||||
ymSendEvent("weather_load_from_cache");
|
||||
|
||||
// Отображаем кэшированные данные
|
||||
displayCurrentWeather(cachedData);
|
||||
setTimeout(() => {
|
||||
lucide.createIcons();
|
||||
}, 100);
|
||||
window.currentHourlyData = cachedData.forecast.forecastday[0].hour;
|
||||
displayTimelapse(
|
||||
cachedData.forecast.forecastday[0].hour,
|
||||
cachedData.location.localtime
|
||||
);
|
||||
displayWeeklyWeather(cachedData.forecast.forecastday);
|
||||
createTemperatureChart(cachedData.forecast.forecastday[0].hour);
|
||||
createPrecipitationChart(cachedData.forecast.forecastday[0].hour);
|
||||
displayAstronomy(cachedData.forecast.forecastday[0].astro);
|
||||
displayAirQuality(cachedData.current.air_quality);
|
||||
updatePageTitle();
|
||||
|
||||
return; // Успешно загружено из кэша
|
||||
} else {
|
||||
console.log("Кэш пуст, данные недоступны");
|
||||
}
|
||||
} catch (cacheError) {
|
||||
console.error("Ошибка чтения кэша:", cacheError);
|
||||
}
|
||||
|
||||
// Fallback: если город не найден и есть координаты, используем их
|
||||
if (userLatitude !== null && userLongitude !== null) {
|
||||
console.log("Пытаемся загрузить погоду по координатам:", {
|
||||
userLatitude,
|
||||
userLongitude,
|
||||
});
|
||||
try {
|
||||
const fallbackUrl = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${userLatitude},${userLongitude}&days=7&hourly=true&aqi=yes&lang=ru`;
|
||||
console.log("Fallback запрос к API:", fallbackUrl);
|
||||
|
||||
const fallbackResponse = await fetch(fallbackUrl);
|
||||
if (fallbackResponse.ok) {
|
||||
const fallbackData = await fallbackResponse.json();
|
||||
console.log("Fallback данные получены успешно");
|
||||
|
||||
// Обновляем город в интерфейсе на реальный из данных
|
||||
const realCityName = fallbackData.location.name;
|
||||
cityInput.value = realCityName;
|
||||
localStorage.setItem("selectedCity", realCityName);
|
||||
updatePageTitle();
|
||||
|
||||
// Отправляем событие успешной fallback загрузки
|
||||
ymSendEvent("weather_load_success_fallback", { city: realCityName });
|
||||
|
||||
// Повторяем отображение с fallback данными
|
||||
displayCurrentWeather(fallbackData);
|
||||
|
||||
setTimeout(() => {
|
||||
lucide.createIcons();
|
||||
}, 100);
|
||||
|
||||
window.currentHourlyData = fallbackData.forecast.forecastday[0].hour;
|
||||
|
||||
displayTimelapse(
|
||||
fallbackData.forecast.forecastday[0].hour,
|
||||
fallbackData.location.localtime
|
||||
);
|
||||
|
||||
displayWeeklyWeather(fallbackData.forecast.forecastday);
|
||||
|
||||
createTemperatureChart(fallbackData.forecast.forecastday[0].hour);
|
||||
createPrecipitationChart(fallbackData.forecast.forecastday[0].hour);
|
||||
|
||||
displayAstronomy(fallbackData.forecast.forecastday[0].astro);
|
||||
|
||||
displayAirQuality(fallbackData.current.air_quality);
|
||||
|
||||
return; // Успешно, выходим
|
||||
} else {
|
||||
console.error("Fallback также не удался");
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error("Ошибка fallback:", fallbackError);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем событие ошибки загрузки
|
||||
ymSendEvent('weather_load_error', {
|
||||
ymSendEvent("weather_load_error", {
|
||||
city: city,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
alert("Не удалось загрузить данные о погоде. Проверьте API ключ.");
|
||||
alert(
|
||||
`Не удалось загрузить данные о погоде для "${city}". Попробуйте другой город или проверьте API ключ. Детали в консоли браузера.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function displayAstronomy(astroData) {
|
||||
const sunriseEl = document.getElementById("sunrise-time");
|
||||
const sunsetEl = document.getElementById("sunset-time");
|
||||
const moonPhaseEl = document.getElementById("moon-phase");
|
||||
const astronomySection = document.getElementById("astronomy-section");
|
||||
|
||||
// Перевод фаз луны на русский
|
||||
const moonPhases = {
|
||||
"New Moon": "Новолуние",
|
||||
"Waxing Crescent": "Растущий серп",
|
||||
"First Quarter": "Первая четверть",
|
||||
"Waxing Gibbous": "Растущая луна",
|
||||
"Full Moon": "Полнолуние",
|
||||
"Waning Gibbous": "Убывающая луна",
|
||||
"Last Quarter": "Последняя четверть",
|
||||
"Waning Crescent": "Убывающий серп",
|
||||
};
|
||||
|
||||
sunriseEl.textContent = convertTo24Hour(astroData.sunrise);
|
||||
sunsetEl.textContent = convertTo24Hour(astroData.sunset);
|
||||
moonPhaseEl.textContent =
|
||||
moonPhases[astroData.moon_phase] || astroData.moon_phase;
|
||||
|
||||
// Показываем секцию
|
||||
astronomySection.classList.remove("hidden");
|
||||
astronomySection.classList.add("animate-fade-in");
|
||||
|
||||
// Отправляем событие просмотра астрономии
|
||||
ymSendEvent("astronomy_view");
|
||||
|
||||
// Пересоздаем иконки
|
||||
setTimeout(() => {
|
||||
lucide.createIcons();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function displayAirQuality(airQualityData) {
|
||||
console.log("Полученные данные качества воздуха:", airQualityData);
|
||||
const aqiValueEl = document.getElementById("aqi-value");
|
||||
const aqiStatusEl = document.getElementById("aqi-status");
|
||||
const aqiDescriptionEl = document.getElementById("aqi-description");
|
||||
const aqiBarEl = document.getElementById("aqi-bar");
|
||||
const airQualitySection = document.getElementById("air-quality-section");
|
||||
|
||||
const aqi = airQualityData["us-epa-index"]; // Используем US EPA индекс
|
||||
console.log("AQI индекс качества воздуха:", aqi);
|
||||
|
||||
// Определяем статус и цвет
|
||||
let status, color, description, percentage;
|
||||
switch (aqi) {
|
||||
case 1:
|
||||
status = "Хороший";
|
||||
color = "#10b981";
|
||||
description = "Качество воздуха хорошее";
|
||||
percentage = 20;
|
||||
break;
|
||||
case 2:
|
||||
status = "Умеренный";
|
||||
color = "#f59e0b";
|
||||
description = "Качество воздуха умеренное";
|
||||
percentage = 40;
|
||||
break;
|
||||
case 3:
|
||||
status = "Плохое";
|
||||
color = "#f97316";
|
||||
description = "Качество воздуха плохое";
|
||||
percentage = 60;
|
||||
break;
|
||||
case 4:
|
||||
status = "Очень плохое";
|
||||
color = "#ef4444";
|
||||
description = "Качество воздуха очень плохое";
|
||||
percentage = 80;
|
||||
break;
|
||||
case 5:
|
||||
status = "Критическое";
|
||||
color = "#7c2d12";
|
||||
description = "Качество воздуха критическое";
|
||||
percentage = 100;
|
||||
break;
|
||||
default:
|
||||
status = "Неизвестно";
|
||||
color = "#6b7280";
|
||||
description = "Данные недоступны";
|
||||
percentage = 0;
|
||||
}
|
||||
|
||||
// Убираем отображение цифры AQI
|
||||
aqiValueEl.style.display = "none";
|
||||
aqiStatusEl.textContent = status;
|
||||
aqiStatusEl.style.color = color;
|
||||
aqiDescriptionEl.textContent = description;
|
||||
aqiBarEl.style.width = `${percentage}%`;
|
||||
aqiBarEl.style.backgroundColor = color;
|
||||
|
||||
// Детальные показатели
|
||||
document.getElementById("pm25").textContent = airQualityData.pm2_5
|
||||
? airQualityData.pm2_5.toFixed(1)
|
||||
: "--";
|
||||
document.getElementById("pm10").textContent = airQualityData.pm10
|
||||
? airQualityData.pm10.toFixed(1)
|
||||
: "--";
|
||||
document.getElementById("no2").textContent = airQualityData.no2
|
||||
? airQualityData.no2.toFixed(1)
|
||||
: "--";
|
||||
document.getElementById("o3").textContent = airQualityData.o3
|
||||
? airQualityData.o3.toFixed(1)
|
||||
: "--";
|
||||
|
||||
// Показываем секцию
|
||||
airQualitySection.classList.remove("hidden");
|
||||
airQualitySection.classList.add("animate-fade-in");
|
||||
|
||||
// Отправляем событие просмотра качества воздуха
|
||||
ymSendEvent("air_quality_view", { aqi: aqi });
|
||||
|
||||
// Пересоздаем иконки
|
||||
setTimeout(() => {
|
||||
lucide.createIcons();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function displayCurrentWeather(data) {
|
||||
const current = data.current;
|
||||
const iconName = getWeatherIcon(current.condition.text);
|
||||
@ -536,10 +1030,32 @@ function displayCurrentWeather(data) {
|
||||
const beautifulName = getBeautifulRussianName(current.condition.text);
|
||||
|
||||
currentTempEl.textContent = `${Math.round(current.temp_c)}°C`;
|
||||
const feelsLikeEl = document.getElementById("current-feels-like");
|
||||
if (feelsLikeEl && current.feelslike_c !== undefined) {
|
||||
feelsLikeEl.textContent = `Ощущается как ${Math.round(
|
||||
current.feelslike_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} км/ч`;
|
||||
|
||||
// Новые поля
|
||||
document.getElementById(
|
||||
"current-gust"
|
||||
).textContent = `Порывы: ${current.gust_kph} км/ч`;
|
||||
document.getElementById("current-vis").textContent = `${current.vis_km} км`;
|
||||
document.getElementById(
|
||||
"current-pressure"
|
||||
).textContent = `${current.pressure_mb} гПа`;
|
||||
document.getElementById("current-uv").textContent = current.uv;
|
||||
|
||||
// Направление ветра
|
||||
const windDirectionIcon = document.getElementById("wind-direction-icon");
|
||||
const windDirEl = document.getElementById("current-wind-dir");
|
||||
windDirEl.textContent = getRussianWindDirection(current.wind_dir);
|
||||
windDirectionIcon.style.transform = `rotate(${current.wind_degree}deg)`;
|
||||
|
||||
// Показываем блок текущей погоды
|
||||
currentWeatherDiv.classList.remove("hidden");
|
||||
currentWeatherDiv.classList.add("animate-fade-in");
|
||||
@ -729,7 +1245,7 @@ function createTemperatureChart(hourlyData) {
|
||||
document.body.classList.contains("dark");
|
||||
|
||||
// Отправляем событие просмотра графика температуры
|
||||
ymSendEvent('chart_view', { chart_type: 'temperature' });
|
||||
ymSendEvent("chart_view", { chart_type: "temperature" });
|
||||
|
||||
// Подготавливаем данные
|
||||
const labels = hourlyData.map((item) => {
|
||||
@ -915,7 +1431,7 @@ function setTheme(theme) {
|
||||
}
|
||||
|
||||
// Отправляем событие смены темы
|
||||
ymSendEvent('theme_change', { theme: theme });
|
||||
ymSendEvent("theme_change", { theme: theme });
|
||||
|
||||
// Сохраняем выбранную тему
|
||||
localStorage.setItem("theme", theme);
|
||||
@ -975,7 +1491,7 @@ function createPrecipitationChart(hourlyData) {
|
||||
const colors = getThemeColors();
|
||||
|
||||
// Отправляем событие просмотра графика осадков
|
||||
ymSendEvent('chart_view', { chart_type: 'precipitation' });
|
||||
ymSendEvent("chart_view", { chart_type: "precipitation" });
|
||||
|
||||
// Подготавливаем данные
|
||||
const labels = hourlyData.map((item) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const CACHE_NAME = 'weather-app-v1';
|
||||
const API_CACHE_NAME = 'weather-api-v1';
|
||||
const CACHE_NAME = 'weather-app-v2';
|
||||
const API_CACHE_NAME = 'weather-api-v2';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user