Compare commits
No commits in common. "38ee07e881cb1707a3402256cdd1f35ea8c40a07" and "ec415de7240b4584fe028fb738a987e8e0577034" have entirely different histories.
38ee07e881
...
ec415de724
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
node_modules/
|
||||
apikey.txt
|
||||
Идеи.txt
|
||||
BIN
@eaDir/README.md@SynoEAStream
Normal file
BIN
@eaDir/README.md@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/index.html@SynoEAStream
Normal file
BIN
@eaDir/index.html@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/input.css@SynoEAStream
Normal file
BIN
@eaDir/input.css@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/output.css@SynoEAStream
Normal file
BIN
@eaDir/output.css@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/package.json@SynoEAStream
Normal file
BIN
@eaDir/package.json@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/postcss.config.js@SynoEAStream
Normal file
BIN
@eaDir/postcss.config.js@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/script.js@SynoEAStream
Normal file
BIN
@eaDir/script.js@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/server.js@SynoEAStream
Normal file
BIN
@eaDir/server.js@SynoEAStream
Normal file
Binary file not shown.
BIN
@eaDir/tailwind.config.js@SynoEAStream
Normal file
BIN
@eaDir/tailwind.config.js@SynoEAStream
Normal file
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 18+)
|
||||
- **Node.js** v8+ (устаревшая версия, рекомендуется обновить до LTS)
|
||||
- **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,7 +157,6 @@
|
||||
|
||||
- **WeatherAPI.com** - источник данных о погоде
|
||||
- **Google Fonts API** - загрузка шрифтов Inter
|
||||
- **Yandex Metrika** - аналитика и отслеживание событий
|
||||
- **CDN сервисы** для библиотек JavaScript (jsDelivr, unpkg)
|
||||
|
||||
## Разработчик
|
||||
@ -180,7 +179,7 @@
|
||||
|
||||
1. Зарегистрироваться на [WeatherAPI.com](https://weatherapi.com)
|
||||
2. Получить бесплатный API ключ
|
||||
3. Добавить ключ в script.js (const API_KEY = "API_KEY_HERE";)
|
||||
3. Добавить ключ в переменные окружения или конфигурацию сервера(server.js - const API_KEY = "API_KEY_HERE";)
|
||||
|
||||
### Производительность
|
||||
|
||||
|
||||
298
index.html
298
index.html
@ -24,25 +24,6 @@
|
||||
/>
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||
|
||||
ym(104563496, "init", {
|
||||
clickmap:true,
|
||||
trackLinks:true,
|
||||
accurateTrackBounce:true,
|
||||
webvisor:true,
|
||||
trackHash:true,
|
||||
ut:"noindex"
|
||||
});
|
||||
</script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/104563496" style="position:absolute; left:-9999px;" alt="" /></div></noscript></search>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
<style>
|
||||
:root {
|
||||
--theme-bg: linear-gradient(to bottom right, #dbeafe, #ffffff, #e0e7ff);
|
||||
@ -318,28 +299,27 @@
|
||||
>
|
||||
<i data-lucide="map-pin" class="w-5 h-5 text-blue-500"></i>
|
||||
<label
|
||||
for="city-search"
|
||||
for="city"
|
||||
class="font-medium text-lg"
|
||||
style="color: var(--theme-text-secondary)"
|
||||
>Выберите город:</label
|
||||
>
|
||||
</div>
|
||||
<input
|
||||
<select
|
||||
id="city"
|
||||
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="Определить мое местоположение"
|
||||
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]"
|
||||
>
|
||||
<i data-lucide="navigation" class="w-5 h-5 text-black dark:text-white"></i>
|
||||
</button>
|
||||
<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>
|
||||
<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"
|
||||
@ -365,8 +345,8 @@
|
||||
<i data-lucide="thermometer" class="w-6 h-6 text-blue-500"></i>
|
||||
Текущая погода
|
||||
</h2>
|
||||
<!-- Температура отдельно в первом ряду -->
|
||||
<div class="text-center mb-6">
|
||||
<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="thermometer"
|
||||
@ -376,13 +356,6 @@
|
||||
--°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"
|
||||
@ -391,9 +364,6 @@
|
||||
Загрузка...
|
||||
</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>
|
||||
@ -431,98 +401,6 @@
|
||||
>
|
||||
-- км/ч
|
||||
</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>
|
||||
@ -531,7 +409,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Timelapse Section -->
|
||||
<div class="timelapse-weather weather-card rounded-2xl shadow-xl p-8 mb-8">
|
||||
<div class="timelapse-weather weather-card rounded-2xl shadow-xl p-8">
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
@ -545,7 +423,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Weekly Weather Section -->
|
||||
<div class="weekly-weather weather-card rounded-2xl shadow-xl p-8 mb-8">
|
||||
<div class="weekly-weather weather-card rounded-2xl shadow-xl p-8 mt-8">
|
||||
<h2
|
||||
class="text-2xl font-bold mb-6 flex items-center gap-2"
|
||||
style="color: var(--theme-text)"
|
||||
@ -561,146 +439,6 @@
|
||||
</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
|
||||
|
||||
612
script.js
612
script.js
@ -1,17 +1,6 @@
|
||||
// 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"),
|
||||
@ -19,11 +8,8 @@ const themeButtons = {
|
||||
dark: document.getElementById("theme-dark"),
|
||||
};
|
||||
|
||||
const cityInput = document.getElementById("city");
|
||||
const cityList = document.getElementById("city-list");
|
||||
let searchTimeout = null;
|
||||
const citySelect = document.getElementById("city");
|
||||
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");
|
||||
@ -31,10 +17,6 @@ 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;
|
||||
|
||||
@ -42,73 +24,6 @@ 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",
|
||||
@ -309,24 +224,6 @@ 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) {
|
||||
// Сначала проверяем русские условия
|
||||
@ -410,149 +307,6 @@ 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");
|
||||
@ -591,94 +345,72 @@ function displayCurrentDate() {
|
||||
|
||||
// Функция для обновления заголовка страницы
|
||||
function updatePageTitle() {
|
||||
const selectedCity = cityInput.value;
|
||||
// Извлечь только название города из "Город, Регион, Страна"
|
||||
const cityName = selectedCity.split(",")[0].trim();
|
||||
document.title = `Погода в ${cityName || "городе"}`;
|
||||
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) {
|
||||
cityInput.value = savedCity;
|
||||
citySelect.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");
|
||||
});
|
||||
|
||||
window.addEventListener("appinstalled", (e) => {
|
||||
console.log("PWA installed successfully");
|
||||
ymSendEvent("pwa_installed");
|
||||
window.addEventListener('appinstalled', (e) => {
|
||||
console.log('PWA installed successfully');
|
||||
});
|
||||
|
||||
// Функция для проверки реального соединения
|
||||
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('Оффлайн режим активирован');
|
||||
} else {
|
||||
banner.classList.add("hidden");
|
||||
console.log("Онлайн режим восстановлен");
|
||||
ymSendEvent("online_mode");
|
||||
banner.classList.add('hidden');
|
||||
console.log('Онлайн режим восстановлен');
|
||||
}
|
||||
}
|
||||
|
||||
@ -686,8 +418,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);
|
||||
@ -710,55 +442,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
getWeatherBtn.addEventListener("click", async () => {
|
||||
const cityFull = cityInput.value.trim();
|
||||
if (!cityFull) {
|
||||
alert("Пожалуйста, введите название города");
|
||||
return;
|
||||
}
|
||||
const city = citySelect.value;
|
||||
|
||||
// Извлечь только название города для API
|
||||
const city = cityFull.split(",")[0].trim();
|
||||
// Сохраняем выбранный город в localStorage
|
||||
localStorage.setItem("selectedCity", city);
|
||||
|
||||
// Отправляем событие выбора города
|
||||
ymSendEvent("city_select", { city: city, cityFull: cityFull });
|
||||
|
||||
// Сохраняем выбранный город в localStorage (полное название для отображения)
|
||||
localStorage.setItem("selectedCity", cityFull);
|
||||
|
||||
let url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&aqi=yes&lang=ru`;
|
||||
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);
|
||||
console.log("API ключ используется:", API_KEY);
|
||||
|
||||
try {
|
||||
let response = await fetch(url);
|
||||
const response = await fetch(url);
|
||||
console.log("Статус ответа:", response.status, response.statusText);
|
||||
console.log(
|
||||
"Заголовки ответа:",
|
||||
Object.fromEntries(response.headers.entries())
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
let errorText = "";
|
||||
try {
|
||||
errorText = await response.text();
|
||||
console.log("Текст ошибки от API:", errorText);
|
||||
} catch (e) {
|
||||
console.log("Не удалось прочитать текст ошибки");
|
||||
}
|
||||
throw new Error(
|
||||
`Ошибка сети: ${response.status} ${response.statusText}${
|
||||
errorText ? ` - ${errorText}` : ""
|
||||
}`
|
||||
);
|
||||
throw new Error(`Ошибка сети: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("Данные получены успешно");
|
||||
|
||||
// Отправляем событие успешной загрузки данных
|
||||
ymSendEvent("weather_load_success", { city: city });
|
||||
|
||||
// Отображаем текущую погоду
|
||||
displayCurrentWeather(data);
|
||||
|
||||
@ -783,12 +485,6 @@ 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) {
|
||||
@ -798,231 +494,10 @@ getWeatherBtn.addEventListener("click", async () => {
|
||||
console.error("Статус ответа:", error.response.status);
|
||||
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", {
|
||||
city: city,
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
alert(
|
||||
`Не удалось загрузить данные о погоде для "${city}". Попробуйте другой город или проверьте API ключ. Детали в консоли браузера.`
|
||||
);
|
||||
alert("Не удалось загрузить данные о погоде. Проверьте 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);
|
||||
@ -1030,32 +505,10 @@ 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");
|
||||
@ -1244,9 +697,6 @@ function createTemperatureChart(hourlyData) {
|
||||
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();
|
||||
@ -1430,9 +880,6 @@ function setTheme(theme) {
|
||||
html.setAttribute("data-theme", "light");
|
||||
}
|
||||
|
||||
// Отправляем событие смены темы
|
||||
ymSendEvent("theme_change", { theme: theme });
|
||||
|
||||
// Сохраняем выбранную тему
|
||||
localStorage.setItem("theme", theme);
|
||||
|
||||
@ -1490,9 +937,6 @@ 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();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const CACHE_NAME = 'weather-app-v2';
|
||||
const API_CACHE_NAME = 'weather-api-v2';
|
||||
const CACHE_NAME = 'weather-app-v1';
|
||||
const API_CACHE_NAME = 'weather-api-v1';
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
|
||||
25
Идеи.txt
25
Идеи.txt
@ -1,25 +0,0 @@
|
||||
Сайт уже имеет хорошую функциональность: текущая погода, часовой таймлапс, недельный прогноз, графики, темы и PWA-поддержку. Вот идеи для дополнения:
|
||||
|
||||
Дополнительные погодные данные
|
||||
Давление, UV-индекс, видимость — в блоке текущей погоды или отдельной карточке.
|
||||
Восход/заход солнца, фаза луны — новая секция с астрономическими данными.
|
||||
Направление ветра, порывы — расширить блок ветра стрелкой и скоростью порывов.
|
||||
Качество воздуха — добавить индекс AQI с цветовой шкалой.
|
||||
Функциональные улучшения
|
||||
Геолокация — кнопка "Определить мое местоположение" для авто-выбора города.
|
||||
Поиск городов — поле ввода вместо селекта, с автодополнением.
|
||||
Единицы измерения — переключатели (°C/°F, км/ч/м/с, мм/дюймы).
|
||||
Сравнение погоды — вкладка для сравнения с вчерашним днём или прошлой неделей.
|
||||
Прогноз на месяц — расширить API-запрос до 30 дней.
|
||||
Визуальные и интерактивные элементы
|
||||
Анимированные иконки — использовать CSS-анимации или Lottie для иконок погоды.
|
||||
Карта погоды — интегрировать OpenWeatherMap или Google Maps с наложением.
|
||||
Тематические фоны — фон сайта меняется в зависимости от погоды (дождь — капли, снег — снежинки).
|
||||
Виджеты для дашборда — мини-версии графиков или карты для главной страницы.
|
||||
Уведомления и интеграции
|
||||
Push-уведомления — предупреждения о дожде, снеге или экстремальной погоде.
|
||||
Экспорт данных — кнопки для скачивания прогноза в PDF/CSV.
|
||||
Социальные функции — поделиться прогнозом в соцсетях.
|
||||
Расширения для PWA
|
||||
Оффлайн-кэширование — хранить последние данные для просмотра без интернета.
|
||||
Виджеты на домашнем экране — мини-версии PWA для iOS/Android.
|
||||
Loading…
x
Reference in New Issue
Block a user