diff --git a/.gitignore b/.gitignore index 40b878d..c1590e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules/ \ No newline at end of file +node_modules/ +apikey.txt \ No newline at end of file diff --git a/README.md b/README.md index a23a49c..fa8ab92 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,127 @@ -# Weather App +# Weather App v1.0.0 -Приложение для просмотра погоды. +Приложение для просмотра погоды с интерактивным интерфейсом и поддержкой тем. ## Описание -Это веб-приложение, которое отображает текущую погоду и почасовой прогноз с использованием API WeatherAPI.com. Интерфейс построен на Tailwind CSS для современного дизайна. +Это веб-приложение отображает текущую погоду, почасовой и недельный прогноз с использованием API WeatherAPI.com. Современный адаптивный интерфейс построен на Tailwind CSS с поддержкой светлой и темной тем. ## Функциональность -- Отображение текущей погоды (температура, влажность, ветер) -- Таймлапс погоды на день -- Адаптивный дизайн -- Выбор городов +- **Текущая погода**: температура, влажность, скорость ветра, описание условий +- **Таймлапс**: почасовой прогноз погоды на день с визуализацией +- **Недельный прогноз**: погода на 7 дней вперед +- **Интерактивные графики**: температура и осадки с помощью Chart.js +- **Адаптивный дизайн**: корректное отображение на всех устройствах +- **Выбор городов**: предустановленный список российских городов +- **Система тем**: светлая, темная и автоматическая (по системным настройкам) +- **Анимации**: плавные переходы и hover-эффекты ## Технологии -- Node.js -- Express -- Tailwind CSS -- HTML5 -- JavaScript -- Swiper.js (для слайдера) -- Chart.js (для графиков) -- Lucide Icons (для иконок) -- Google Fonts (Inter) +### Backend -## Установка +- **Node.js** v8+ (устаревшая версия, рекомендуется обновить до LTS) +- **Express.js** v4.17.1 - веб-сервер + +### Frontend + +- **HTML5** - семантическая разметка +- **JavaScript** (Vanilla) - клиентская логика +- **Tailwind CSS** v3.4.16 - утилитарный CSS фреймворк +- **PostCSS** v8.5.6 - обработка CSS +- **Autoprefixer** v10.4.21 - вендорные префиксы + +### Библиотеки и CDN + +- **Lucide Icons** - современные SVG иконки +- **Swiper.js** v11 - слайдер для таймлапса +- **Chart.js** - построение графиков +- **Google Fonts** (Inter) - шрифты + +### Стилизация + +- CSS переменные для тематизации +- Плавные анимации переходов +- Цветовые схемы для разных погодных условий + +## Установка и запуск + +### Системные требования + +- **Node.js** v8+ (⚠️ устаревшая версия, рекомендуется использовать LTS версию Node.js 18+) +- **npm** для управления пакетами + +### Установка + +1. **Клонируйте репозиторий:** -1. Клонируйте репозиторий: ```bash - git clone + git clone cd weather-app ``` -2. Установите зависимости: +2. **Установите зависимости:** + ```bash npm install ``` -3. Запустите сервер: +3. **Соберите CSS (если необходимо):** + + ```bash + npm run build-css + ``` + +4. **Запустите сервер:** + ```bash npm start ``` -4. Откройте http://localhost:3000 в браузере. +5. **Откройте браузер:** + Перейдите по адресу http://localhost:3000 -## Структура проекта +### Доступные скрипты -- `server.js` - сервер на Express -- `index.html` - главная страница -- `script.js` - клиентский JavaScript -- `package.json` - конфигурация +- `npm start` - запуск сервера разработки +- `npm run build-css` - сборка Tailwind CSS из `input.css` в `output.css` +- `npm run watch-css` - автоматическая пересборка CSS при изменениях + +## API и внешние сервисы + +- **WeatherAPI.com** - источник данных о погоде +- **Google Fonts API** - загрузка шрифтов Inter +- **CDN сервисы** для библиотек JavaScript (jsDelivr, unpkg) + +## Разработчик + +Разработано: **Fovway** + +Контакты: + +- Email: [admin@fovway.ru](mailto:admin@fovway.ru) + +## Рекомендации + +### Обновление Node.js + +Текущая конфигурация требует Node.js v8, но рекомендуется использовать актуальную LTS версию (18+) для повышения безопасности и производительности. + +### Настройка API ключа + +Для полноценной работы приложения необходимо: + +1. Зарегистрироваться на [WeatherAPI.com](https://weatherapi.com) +2. Получить бесплатный API ключ +3. Добавить ключ в переменные окружения или конфигурацию сервера(server.js - const API_KEY = "API_KEY_HERE";) + +### Производительность + +- Приложение использует CDN для внешних библиотек, что обеспечивает быструю загрузку +- CSS собирается с помощью Tailwind CSS для оптимального размера файлов +- Используйте `npm run watch-css` во время разработки для автоматической пересборки стилей ## Лицензия -MIT \ No newline at end of file +MIT diff --git a/index.html b/index.html index 78c0959..467e224 100644 --- a/index.html +++ b/index.html @@ -1,201 +1,496 @@ - - - + + + Погода - - - + + + - + - - + +
- -
-
-
- -
-

Погода

-
-

Узнайте погоду в вашем городе

-
- - Загрузка... -
-
- - -
-
-
- - -
- - -
+ +
+
+
+ +
+

+ Погода +

+
+

+ Узнайте погоду в вашем городе +

+
+ + Загрузка...
- -
- - - - -
-

- - Погода на весь день -

-
-
-
-
- - -
-

- - Погода на неделю -

-
- -
-
- - -
-

- - Графики погоды -

-
- -
-

- - Температура (°C) -

- -
- - -
-

- - Осадки (мм) -

- -
-
-
+ +
+
+ + + +
+
+ + +
+
+
+ + +
+ + +
+
+ + +
+ + + + +
+

+ + Погода на весь день +

+
+
+
+
+ + +
+

+ + Погода на неделю +

+
+ +
+
+ + +
+

+ + Графики погоды +

+
+ +
+

+ + Температура (°C) +

+ +
+ + +
+

+ + Осадки (мм) +

+ +
+
+
+
- - \ No newline at end of file + + diff --git a/input.css b/input.css new file mode 100644 index 0000000..7cccacb --- /dev/null +++ b/input.css @@ -0,0 +1,16 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Custom styles */ +.weather-card { + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); + border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.1); +} + +.dark .weather-card { + background-color: rgb(30 41 55 / var(--tw-bg-opacity, 1)); + border-color: rgb(71 85 105 / var(--tw-border-opacity, 1)); + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.3), 0 10px 10px -5px rgba(0,0,0,0.3); +} \ No newline at end of file diff --git a/output.css b/output.css new file mode 100644 index 0000000..f39c5ac --- /dev/null +++ b/output.css @@ -0,0 +1,1187 @@ +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +/* +! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden]:where(:not([hidden="until-found"])) { + display: none; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.relative { + position: relative; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-3 { + height: 0.75rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-8 { + height: 2rem; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-3 { + width: 0.75rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-8 { + width: 2rem; +} + +.min-w-\[200px\] { + min-width: 200px; +} + +.max-w-7xl { + max-width: 80rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-3 { + gap: 0.75rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.gap-8 { + gap: 2rem; +} + +.rounded-2xl { + border-radius: 1rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.border { + border-width: 1px; +} + +.border-2 { + border-width: 2px; +} + +.border-blue-300 { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity, 1)); +} + +.border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); +} + +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity, 1)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); +} + +.bg-blue-50 { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1)); +} + +.bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} + +.bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); +} + +.from-blue-100 { + --tw-gradient-from: #dbeafe var(--tw-gradient-from-position); + --tw-gradient-to: rgb(219 234 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.from-blue-50 { + --tw-gradient-from: #eff6ff var(--tw-gradient-from-position); + --tw-gradient-to: rgb(239 246 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.to-indigo-100 { + --tw-gradient-to: #e0e7ff var(--tw-gradient-to-position); +} + +.to-indigo-50 { + --tw-gradient-to: #eef2ff var(--tw-gradient-to-position); +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-8 { + padding: 2rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.text-center { + text-align: center; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity, 1)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity, 1)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity, 1)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity, 1)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring-2 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-blue-200 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity, 1)); +} + +.ring-blue-300 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity, 1)); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +/* Custom styles */ + +.weather-card { + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); + border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.1); +} + +.dark .weather-card { + background-color: rgb(30 41 55 / var(--tw-bg-opacity, 1)); + border-color: rgb(71 85 105 / var(--tw-border-opacity, 1)); + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.3), 0 10px 10px -5px rgba(0,0,0,0.3); +} + +.hover\:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:border-blue-300:hover { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity, 1)); +} + +.hover\:bg-blue-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); +} + +.hover\:text-blue-600:hover { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); +} + +.hover\:shadow-lg:hover { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.hover\:shadow-xl:hover { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.focus\:border-blue-500:focus { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-200:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity, 1)); +} + +.dark\:border-blue-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity, 1)); +} + +.dark\:border-gray-600:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity, 1)); +} + +.dark\:bg-blue-900\/20:is(.dark *) { + background-color: rgb(30 58 138 / 0.2); +} + +.dark\:bg-gray-700:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1)); +} + +.dark\:from-blue-900\/30:is(.dark *) { + --tw-gradient-from: rgb(30 58 138 / 0.3) var(--tw-gradient-from-position); + --tw-gradient-to: rgb(30 58 138 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.dark\:from-gray-700:is(.dark *) { + --tw-gradient-from: #374151 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(55 65 81 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.dark\:to-gray-800:is(.dark *) { + --tw-gradient-to: #1f2937 var(--tw-gradient-to-position); +} + +.dark\:to-indigo-900\/30:is(.dark *) { + --tw-gradient-to: rgb(49 46 129 / 0.3) var(--tw-gradient-to-position); +} + +.dark\:text-blue-300:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity, 1)); +} + +.dark\:text-blue-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity, 1)); +} + +.dark\:text-gray-200:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity, 1)); +} + +.dark\:text-gray-300:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity, 1)); +} + +.dark\:text-gray-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity, 1)); +} + +.dark\:ring-blue-600:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity, 1)); +} + +.dark\:hover\:border-blue-600:hover:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(37 99 235 / var(--tw-border-opacity, 1)); +} + +@media (min-width: 768px) { + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:text-5xl { + font-size: 3rem; + line-height: 1; + } +} + +@media (min-width: 1024px) { + .lg\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bf965cf..20e81cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,123 @@ "express": "^4.17.1" }, "devDependencies": { - "tailwindcss": "^4.1.14" + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.16" }, "engines": { "node": "8" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -29,11 +140,124 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", + "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -57,6 +281,60 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -92,6 +370,98 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001748", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", + "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -124,6 +494,32 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -149,6 +545,18 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -162,11 +570,29 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/electron-to-chromium": { + "version": "1.5.233", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz", + "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -202,6 +628,15 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -260,6 +695,55 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -277,6 +761,22 @@ "node": ">= 0.8" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -285,6 +785,19 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -293,6 +806,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -336,6 +863,38 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -408,6 +967,126 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -432,6 +1111,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -440,6 +1128,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -470,11 +1171,64 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -483,6 +1237,48 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -505,6 +1301,12 @@ "node": ">= 0.8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -513,11 +1315,234 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -544,6 +1569,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -566,6 +1611,80 @@ "node": ">= 0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -645,6 +1764,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -713,6 +1853,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -721,12 +1882,206 @@ "node": ">= 0.8" } }, - "node_modules/tailwindcss": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", - "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -735,6 +2090,12 @@ "node": ">=0.6" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -755,6 +2116,42 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -770,6 +2167,112 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 00da365..0731d46 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "Weather app with timelapse", "main": "server.js", "scripts": { - "start": "node server.js" + "start": "node server.js", + "build-css": "tailwindcss -i input.css -o output.css", + "watch-css": "tailwindcss -i input.css -o output.css --watch" }, "engines": { "node": "8" @@ -13,6 +15,8 @@ "express": "^4.17.1" }, "devDependencies": { - "tailwindcss": "^4.1.14" + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.16" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/script.js b/script.js index adc3a5e..f11728c 100644 --- a/script.js +++ b/script.js @@ -1,14 +1,21 @@ // API ключ от WeatherAPI -const API_KEY = '485eff906f7d473b913104046250710'; +const API_KEY = "API_KEY_HERE"; -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'); +// Глобальные переменные для темы +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; @@ -19,525 +26,1019 @@ 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' + 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 currentDateEl = document.getElementById("current-date"); + const now = new Date(); - const daysOfWeek = ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота']; - const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']; + const daysOfWeek = [ + "воскресенье", + "понедельник", + "вторник", + "среда", + "четверг", + "пятница", + "суббота", + ]; + const months = [ + "января", + "февраля", + "марта", + "апреля", + "мая", + "июня", + "июля", + "августа", + "сентября", + "октября", + "ноября", + "декабря", + ]; - const dayOfWeek = daysOfWeek[now.getDay()]; - const day = now.getDate(); - const month = months[now.getMonth()]; + const dayOfWeek = daysOfWeek[now.getDay()]; + const day = now.getDate(); + const month = months[now.getMonth()]; - currentDateEl.textContent = `${dayOfWeek}, ${day} ${month}`; + currentDateEl.textContent = `${dayOfWeek}, ${day} ${month}`; } // Функция для обновления заголовка страницы function updatePageTitle() { - const selectedCity = citySelect.options[citySelect.selectedIndex].text; - document.title = `Погода в ${selectedCity}`; + const selectedCity = citySelect.options[citySelect.selectedIndex].text; + document.title = `Погода в ${selectedCity}`; } // Автоматическая загрузка погоды при загрузке страницы -document.addEventListener('DOMContentLoaded', () => { - // Отображаем текущую дату - displayCurrentDate(); +document.addEventListener("DOMContentLoaded", () => { + // Отображаем текущую дату + displayCurrentDate(); - // Загружаем сохраненный город из localStorage - const savedCity = localStorage.getItem('selectedCity'); - if (savedCity) { - citySelect.value = savedCity; - } + // Загружаем сохраненный город из localStorage + const savedCity = localStorage.getItem("selectedCity"); + if (savedCity) { + citySelect.value = savedCity; + } - // Обновляем заголовок страницы - updatePageTitle(); + // Обновляем заголовок страницы + updatePageTitle(); - // Ждем загрузки Swiper - if (typeof Swiper !== 'undefined') { + // Инициализируем тему + initializeTheme(); + watchSystemTheme(); + + // Сохраняем данные для графиков + window.currentHourlyData = null; + + // Ждем загрузки Swiper + if (typeof Swiper !== "undefined") { + getWeatherBtn.click(); + } else { + // Если Swiper еще не загрузился, ждем его + const checkSwiper = setInterval(() => { + if (typeof Swiper !== "undefined") { + clearInterval(checkSwiper); getWeatherBtn.click(); - } else { - // Если Swiper еще не загрузился, ждем его - const checkSwiper = setInterval(() => { - if (typeof Swiper !== 'undefined') { - clearInterval(checkSwiper); - getWeatherBtn.click(); - } - }, 100); - } + } + }, 100); + } }); -getWeatherBtn.addEventListener('click', async () => { - const city = citySelect.value; +getWeatherBtn.addEventListener("click", async () => { + const city = citySelect.value; - // Сохраняем выбранный город в localStorage - localStorage.setItem('selectedCity', 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`; + const url = `https://api.weatherapi.com/v1/forecast.json?key=${API_KEY}&q=${city}&days=7&hourly=true&lang=ru`; - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Ошибка сети'); - } - const data = await response.json(); + console.log("Запрос к API:", url); + console.log("Выбранный город:", city); - // Отображаем текущую погоду - displayCurrentWeather(data); - - // Пересоздаем иконки после обновления DOM - setTimeout(() => { - lucide.createIcons(); - }, 100); - - // Таймлапс на сегодня - 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); - alert('Не удалось загрузить данные о погоде. Проверьте API ключ.'); + 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("Данные получены успешно"); + + // Отображаем текущую погоду + 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); + } + alert("Не удалось загрузить данные о погоде. Проверьте API ключ."); + } }); function displayCurrentWeather(data) { - const current = data.current; - currentTempEl.textContent = `${Math.round(current.temp_c)}°C`; - currentDescEl.textContent = current.condition.text; - currentHumidityEl.textContent = `${current.humidity}%`; - currentWindEl.textContent = `${current.wind_kph} км/ч`; + const current = data.current; + const iconName = getWeatherIcon(current.condition.text); + const iconColorClass = getWeatherIconColor(iconName); + const beautifulName = getBeautifulRussianName(current.condition.text); - // Показываем блок текущей погоды - currentWeatherDiv.classList.remove('hidden'); - currentWeatherDiv.classList.add('animate-fade-in'); + currentTempEl.textContent = `${Math.round(current.temp_c)}°C`; + currentDescEl.innerHTML = `${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; + 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; + 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 p-3 rounded-xl shadow-md border ${isCurrent ? 'border-blue-500 bg-gradient-to-br from-blue-100 to-indigo-100 ring-2 ring-blue-300' : 'border-gray-100'}`; - slide.style.width = '140px'; - const iconName = weatherIcons[item.condition.text] || 'cloud'; - slide.innerHTML = ` + 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 = `
-

${hour}:00 ${isCurrent ? '(сейчас)' : ''}

+

${hour}:00 ${isCurrent ? "(сейчас)" : ""}

- +
-

${Math.round(item.temp_c)}°C

-

${item.condition.text}

-

💧 ${item.precip_mm} мм

-

☔ ${item.chance_of_rain}%

+

${Math.round(item.temp_c)}°C

+

${getBeautifulRussianName(item.condition.text)}

+

💧 ${ + item.precip_mm + } мм

+

☔ ${ + item.chance_of_rain + }%

`; - timelapseContainer.appendChild(slide); - }); + timelapseContainer.appendChild(slide); + }); - // Пересоздаем иконки - setTimeout(() => { - lucide.createIcons(); - }, 100); + // Пересоздаем иконки + 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(); - } + // Инициализируем 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); + // Фокус на текущем времени города + if (currentSlideIndex !== -1) { + setTimeout(() => { + weatherSwiper.slideTo(currentSlideIndex, 800); + }, 200); + } + }, 50); } function displayWeeklyWeather(forecastData) { - const weeklyContainer = document.getElementById('weekly-container'); - weeklyContainer.innerHTML = ''; + const weeklyContainer = document.getElementById("weekly-container"); + weeklyContainer.innerHTML = ""; - const daysOfWeek = ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб']; + 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' }); + 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 = weatherIcons[day.day.condition.text] || 'cloud'; - const isToday = index === 0; + const iconName = getWeatherIcon(day.day.condition.text); + const iconColorClass = getWeatherIconColor(iconName); + const isToday = index === 0; - 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 border-blue-300 ring-2 ring-blue-200' - : 'bg-gray-50 border-gray-200 hover:border-blue-300' - }`; + // Отладочная информация + console.log( + `Weekly: ${isToday ? "Сегодня" : dayOfWeek} - Условие: "${ + day.day.condition.text + }" - Иконка: "${iconName}" - Цвет: "${iconColorClass}"` + ); - card.innerHTML = ` + 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 = `
-

- ${isToday ? 'Сегодня' : dayOfWeek} +

+ ${isToday ? "Сегодня" : dayOfWeek}

-

${dayNumber} ${month}

+

${dayNumber} ${month}

- +
-

+

${Math.round(day.day.maxtemp_c)}°

-

+

${Math.round(day.day.mintemp_c)}°

-

- ${day.day.condition.text} +

+ ${getBeautifulRussianName(day.day.condition.text)}

`; - weeklyContainer.appendChild(card); - }); + weeklyContainer.appendChild(card); + }); - // Пересоздаем иконки для недельного прогноза - setTimeout(() => { - lucide.createIcons(); - }, 100); + // Пересоздаем иконки для недельного прогноза + setTimeout(() => { + lucide.createIcons(); + }, 100); } // Функция создания графика температуры function createTemperatureChart(hourlyData) { - const ctx = document.getElementById('temperatureChart').getContext('2d'); + const ctx = document.getElementById("temperatureChart").getContext("2d"); + const colors = getThemeColors(); + const isDark = + document.documentElement.classList.contains("dark") || + document.body.classList.contains("dark"); - // Подготавливаем данные - const labels = hourlyData.map(item => { - const hour = new Date(item.time).getHours(); - return `${hour}:00`; - }); + // Подготавливаем данные + 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)); + const temperatures = hourlyData.map((item) => Math.round(item.temp_c)); - // Уничтожаем предыдущий график если он существует - if (temperatureChart) { - temperatureChart.destroy(); - } + // Уничтожаем предыдущий график если он существует + 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 - }] + // Создаем новый график + 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: 'rgba(0, 0, 0, 0.8)', - titleColor: '#ffffff', - bodyColor: '#ffffff', - cornerRadius: 8, - displayColors: false, - callbacks: { - title: function(context) { - return `Время: ${context[0].label}`; - }, - label: function(context) { - return `Температура: ${context.parsed.y}°C`; - } - } - } + ], + }, + 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}`; }, - scales: { - x: { - grid: { - display: false - }, - ticks: { - color: '#6b7280', - font: { - size: 12 - } - } - }, - y: { - grid: { - color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1 - }, - ticks: { - color: '#6b7280', - font: { - size: 12 - }, - callback: function(value) { - return value + '°C'; - } - } - } + label: function (context) { + return `Температура: ${context.parsed.y}°C`; }, - animation: { - duration: 2000, - easing: 'easeInOutQuart' + }, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: colors.textColor, + font: { + size: 12, + color: colors.textColor, }, - interaction: { - intersect: false, - mode: 'index' - } - } + }, + }, + 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"); + } + + // Сохраняем выбранную тему + 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 ctx = document.getElementById("precipitationChart").getContext("2d"); + const colors = getThemeColors(); - // Подготавливаем данные - const labels = hourlyData.map(item => { - const hour = new Date(item.time).getHours(); - return `${hour}:00`; - }); + // Подготавливаем данные + 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); + const precipitations = hourlyData.map((item) => item.precip_mm); + const chances = hourlyData.map((item) => item.chance_of_rain); - // Уничтожаем предыдущий график если он существует - if (precipitationChart) { - precipitationChart.destroy(); - } + // Уничтожаем предыдущий график если он существует + 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' - }] + // Создаем новый график + 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, }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - display: false - }, - tooltip: { - backgroundColor: 'rgba(0, 0, 0, 0.8)', - titleColor: '#ffffff', - bodyColor: '#ffffff', - 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}%`; - } - } - } - } + { + 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}`; }, - scales: { - x: { - grid: { - display: false - }, - ticks: { - color: '#6b7280', - font: { - size: 12 - } - } - }, - y: { - type: 'linear', - display: true, - position: 'left', - grid: { - color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1 - }, - ticks: { - color: '#6b7280', - font: { - size: 12 - }, - 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 - }, - callback: function(value) { - return value + '%'; - } - }, - title: { - display: true, - text: 'Вероятность (%)', - color: '#ef4444', - font: { - size: 14, - weight: 'bold' - } - } - } + label: function (context) { + if (context.datasetIndex === 0) { + return `Осадки: ${context.parsed.y} мм`; + } else { + return `Вероятность: ${context.parsed.y}%`; + } }, - animation: { - duration: 2000, - easing: 'easeInOutQuart', - delay: function(context) { - return context.dataIndex * 100; - } + }, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: colors.textColor, + font: { + size: 12, + color: colors.textColor, }, - interaction: { - intersect: false, - mode: 'index' - } - } - }); + }, + }, + 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", + }, + }, + }); } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..3dad926 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./index.html", "./script.js"], + darkMode: 'class', + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file