diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml
index e55cc27..df062df 100644
--- a/.github/workflows/django.yml
+++ b/.github/workflows/django.yml
@@ -12,7 +12,10 @@ jobs:
env:
SECRET_KEY: ${{ secrets.SECRET_KEY }}
- API_KEY: ${{ secrets.API_KEY }}
+ API_KEY: ${{ secrets.API_KEY }}
+ NEWS_API_KEY: ${{ secrets.NEWS_API_KEY }}
+ WEATHER_API_KEY: ${{ secrets.WEATHER_API_KEY }}
+ WEATHER_API_KEY_2: ${{ secrets.WEATHER_API_KEY_2 }}
strategy:
max-parallel: 4
@@ -33,6 +36,11 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install --no-cache-dir -r requirements.txt
+ - name: Collect static files
+ run: |
+ cd backend
+ python manage.py collectstatic --no-input
- name: Run Tests
run: |
+ cd backend
python manage.py test
diff --git a/.gitignore b/.gitignore
index c2111fc..624a8fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,38 @@
-.env
-.vscode
\ No newline at end of file
+# Backend
+*.env
+.vscode/
+__pycache__/
+*.pyc
+db.sqlite3
+log.txt
+staticfiles/
+cache/*.djcache
+*.djcache
+
+# Frontend
+node_modules/
+build/
+dist/
+*.log
+.pnp/
+.pnp.js
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Testing
+coverage/
+
+# Production
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Miscellaneous
+*.swp
+.idea/
+*.iml
+*.lock
+
diff --git a/README.md b/README.md
index 9997422..2becc6c 100644
--- a/README.md
+++ b/README.md
@@ -1,151 +1,98 @@
![baner](https://github.com/Ghosts6/Local-website/blob/main/img/Baner.png)
-# ⛈️ weather_news:
+# 🌍 Django Weather App with Search Functionality
-here we have my weather project which created with help of django and include pages like home and weather,home page: inside this page you can check out weather news about
-cities around word,storm ,torando,flood ,weather in your place and etc also there are links so if something was interesting for you,you could read more about it.
-weather page: this page created for searching and check out weather in differend locations ,in this page by searching city name you will recive data about location like
-weather,temperature,wind speed,time and etc
+This Django application allows users to search for weather information in different locations. By entering a city name, users can view real-time details like temperature, description, wind speed, and humidity. The app also displays current weather icons based on the conditions (e.g., sunny, rainy, snowy, etc.).
-# 💻 Technologies:
+## Key Features:
+- **Weather Search**: Search for weather information by city name.
+- **Real-Time Data**: View temperature, wind speed, humidity, and weather description.
+- **Weather Icons**: Visual representation of weather conditions (sun, rain, snow, etc.).
+- **Time Zone Integration**: Displays the current time for the searched city.
+- **Dark Mode**: Option to toggle dark mode for better readability.
-for this projec i use django and api like drf and rest api for backend and for frontend i use js/html/css for customize and create pages
+Our goal is to work with APIs, manage their integration, and handle key concepts such as sending and receiving data between the backend and frontend. This project focuses on fetching data from external providers through their APIs, allowing you to understand how to interact with external services and present dynamic information in a web application.
- ![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=plastic&logo=css3&logoColor=white) ![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=plastic&logo=html5&logoColor=white) ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=plastic&logo=javascript&logoColor=%23F7DF1E) ![Python](https://img.shields.io/badge/python-3670A0?style=plastic&logo=python&logoColor=ffdd54) ![Django](https://img.shields.io/badge/django-%23092E20.svg?style=plastic&logo=django&logoColor=white) ![REST API](https://img.shields.io/badge/REST%20API-%2320232a.svg?style=plastic&logo=api&logoColor=white) ![RESTful API](https://img.shields.io/badge/RESTful%20API-%2320232a.svg?style=plastic&logo=api&logoColor=white) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-%23336791.svg?style=plastic&logo=postgresql&logoColor=white)
+---
+## 💻 Technologies Used:
+
+### Technologies Used:
+- ![Django](https://img.shields.io/badge/django-%23092E20.svg?style=plastic&logo=django&logoColor=white) – Backend framework for scalable web applications.
+- ![Python](https://img.shields.io/badge/python-3670A0?style=plastic&logo=python&logoColor=ffdd54) – The programming language powering the backend logic.
+- ![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=plastic&logo=tailwind-css&logoColor=white) – A utility-first CSS framework for sleek, responsive designs.
+- ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=plastic&logo=javascript&logoColor=%23F7DF1E) – Adds interactivity and dynamic behavior to the frontend.
+- ![Font Awesome](https://img.shields.io/badge/font%20awesome-%23F7DF1E.svg?style=plastic&logo=font-awesome&logoColor=black) – A library of scalable vector icons for visual elements.
+- ![DRF](https://img.shields.io/badge/DRF-%2300B8A1.svg?style=plastic&logo=django&logoColor=white) – For building powerful REST APIs.
+- ![Redis](https://img.shields.io/badge/redis-%23D72C25.svg?style=plastic&logo=redis&logoColor=white) – In-memory data store for caching and message brokering.
+- ![pytest](https://img.shields.io/badge/pytest-%232C2A29.svg?style=plastic&logo=pytest&logoColor=white) – A robust testing framework for ensuring code reliability.
+- ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-%23336791.svg?style=plastic&logo=postgresql&logoColor=white) – Relational database system, where applicable for data storage.
+
+---
# 🧑💻code_sample:
-in this section we will take closer look to weather page which created for checking cities weather by searching they name
-
-weather.html
-```html
-{% load static%}
-
-
-
-
-
-
-
-
-
-
-
-
-
- search bar
-
-
-
-
-
-
-
-
-
-
-
-
- {% if city_name %}
-
Weather in {{ city_name }}
-
Time: {{ city_time }}
-
Temperature: {{ temperature }}°C
-
Description: {{ description }}
-
WindSpeed: {{ wind_speed }}
-
Humidity: {{ humidity }}
- {% endif %}
-
-
-
-
-
-
-
-
-
-
-
- {% if rainy_weather %}
-
- {% elif sunny_weather %}
-
- {% elif snowy_weather %}
-
- {% elif tornado_weather %}
-
- {% elif cloudy_weather %}
-
- {% elif windy_weather %}
-
- {% elif moon_weather %}
-
- {% else %}
-
- {% endif %}
-
-
-
-
- Home
-
-
-
-
-
-```
-views.py
-```python
-def weather(request):
- city_name = request.GET.get('city_name', '')
-
- if city_name:
+In this section, we'll show you how we fetch weather data from an external API and send it to the frontend using Django's backend. The frontend will use JavaScript (fetch) to get the data asynchronously and display it to the user.
- api_key = '0f00a482322f82d0c38ea32028a6eada'
- weather_url = f'http://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}'
- time_url = f'http://worldtimeapi.org/api/timezone/Europe/{city_name}.json'
+## Backend: Django API View
- try:
+Here’s the backend code, which integrates the backend API call to fetch weather data and explains how it interacts with the frontend using fetch and JSON. The backend API receives the city name, queries external weather services for data, and sends back a structured JSON response. The JavaScript fetch function on the frontend then calls this API, retrieves the data, and updates the webpage accordingly. By using JSON format, both the frontend and backend can communicate seamlessly, ensuring the correct information is displayed to the user in real time.:
+```py
+def get_weather_data(request):
+ city_name = request.GET.get('city_name', '').strip()
+
+ if city_name:
+ weather_url = f'http://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={API_KEY}'
+ weatherapi_url = f'https://api.weatherapi.com/v1/forecast.json?key={WEATHER_API_KEY_2}&q={city_name}&hours=12'
+
+ def fetch_with_retry(url, retries=MAX_RETRIES):
+ for attempt in range(retries):
+ try:
+ response = requests.get(url, timeout=TIMEOUT)
+ response.raise_for_status()
+ return response
+ except requests.RequestException as e:
+ if attempt < retries - 1:
+ time.sleep(2 ** attempt)
+ else:
+ raise e
- weather_response = requests.get(weather_url)
+ try:
+ # Fetch data from OpenWeatherMap API
+ weather_response = fetch_with_retry(weather_url)
weather_data = weather_response.json()
- time_response = requests.get(time_url)
- time_data = time_response.json()
+ if weather_data.get('cod') != 200:
+ return JsonResponse({'error_message': 'City Not Found'}, status=404)
- temperature = weather_data.get('main', {}).get('temp', '')
+ # Extract necessary weather details
+ temperature = kelvin_to_celsius(weather_data.get('main', {}).get('temp', 0))
description = weather_data.get('weather', [{}])[0].get('description', '')
icon = weather_data.get('weather', [{}])[0].get('icon', '')
- wind_speed = weather_data.get('wind', {}).get('speed', '')
- humidity = weather_data.get('main', {}).get('humidity', '')
-
-
- city_timezone = time_data.get('timezone', '')
- city_time_utc = time_data.get('utc_datetime', '')
-
- if city_timezone and city_time_utc:
- utc_time = datetime.strptime(city_time_utc, '%Y-%m-%dT%H:%M:%S.%fZ')
- city_time = utc_time.astimezone(pytz.timezone(city_timezone)).strftime('%Y-%m-%d %H:%M:%S')
- else:
- city_time = ''
-
-
- rainy_weather = 'rain' in description.lower()
- sunny_weather = 'clear' in description.lower() or 'sun' in description.lower()
- snowy_weather = 'snow' in description.lower()
- tornado_weather = 'tornado' in description.lower()
- cloudy_weather = 'cloud' in description.lower()
- windy_weather = 'wind' in description.lower()
- moon_weather = 'moon' in description.lower()
-
- return render(request, 'weather.html', {
+ wind_speed = weather_data.get('wind', {}).get('speed', 0)
+ humidity = weather_data.get('main', {}).get('humidity', 0)
+
+ city_timezone = weather_data.get('timezone', 0)
+ utc_time = datetime.utcnow() + timedelta(seconds=city_timezone)
+ city_time = utc_time.strftime('%Y-%m-%d %H:%M:%S')
+
+ # Fetch data from WeatherAPI for hourly forecast
+ weatherapi_response = fetch_with_retry(weatherapi_url)
+ weatherapi_data = weatherapi_response.json()
+
+ hourly_forecast = []
+ if 'forecast' in weatherapi_data and 'forecastday' in weatherapi_data['forecast']:
+ for hour in weatherapi_data['forecast']['forecastday'][0]['hour'][:12]:
+ hour_data = {
+ 'time': hour['time'].split(' ')[1],
+ 'temperature': hour['temp_c'],
+ 'description': hour['condition']['text'],
+ 'icon': hour['condition']['icon'],
+ }
+ hourly_forecast.append(hour_data)
+
+ # Prepare response data to send to frontend
+ response_data = {
'city_name': city_name,
'temperature': temperature,
'description': description,
@@ -153,58 +100,156 @@ def weather(request):
'city_time': city_time,
'wind_speed': wind_speed,
'humidity': humidity,
- 'rainy_weather': rainy_weather,
- 'sunny_weather': sunny_weather,
- 'snowy_weather': snowy_weather,
- 'tornado_weather': tornado_weather,
- 'cloudy_weather': cloudy_weather,
- 'windy_weather': windy_weather,
- 'moon_weather': moon_weather,
- })
+ 'timezone': city_timezone,
+ 'hourly_forecast': hourly_forecast,
+ }
+ return JsonResponse(response_data)
except Exception as e:
- error_message = f'Error fetching data: {str(e)}'
- return render(request, 'weather.html', {'error_message': error_message})
+ return JsonResponse({'error_message': f'Error fetching data: {str(e)}'}, status=500)
- return render(request, 'weather.html')
-def search_weather(request):
- city_name = request.GET.get('city_name', '')
- api_key = '0f00a482322f82d0c38ea32028a6eada'
+ return JsonResponse({'error_message': 'City name is required'}, status=400)
- if city_name and api_key:
- try:
- api_url = f'https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}'
- response = requests.get(api_url)
- data = response.json()
-
- if response.status_code == 200:
- weather_data = {
- 'temperature': data['main']['temp'],
- 'description': data['weather'][0]['description'],
- 'icon': data['weather'][0]['icon']
- }
- return JsonResponse({'success': True, 'weather_data': weather_data})
- else:
- return JsonResponse({'success': False, 'error': 'Unable to fetch weather data'})
- except Exception as e:
- return JsonResponse({'success': False, 'error': str(e)})
- else:
- return JsonResponse({'success': False, 'error': 'City name or API key is missing'})
-def search_suggestions(request):
- city_name = request.GET.get('city_name', '').lower()
-
- try:
- json_path = '/home/ghost/Desktop/webProject/virtual/climate/climate/fixture/city.list.json'
-
- with open(json_path, 'r', encoding='utf-8') as file:
- cities = json.load(file)
-
- suggestions = [city['name'] for city in cities if city_name in city['name'].lower()]
- return JsonResponse({'success': True, 'suggestions': suggestions})
- except Exception as e:
- return JsonResponse({'success': False, 'error': str(e)})
+def kelvin_to_celsius(kelvin):
+ return kelvin - 273.15 # Convert temperature from Kelvin to Celsius
+```
+
+## Frontend: JavaScript Code to Fetch and Display Data
+
+The following JavaScript code is designed to interact with the backend of weather application. It sends an HTTP request to the backend to fetch weather data for a specified city. The request is made using the fetch function, which communicates with the Django backend API endpoint that retrieves weather data. Once the backend responds, the weather data is returned in JSON format. The frontend then processes this data to dynamically update the user interface, displaying details like the current temperature, weather description, and hourly forecasts for the chosen city.
+```py
+function fetchWeather(cityName) {
+ fetch(`/get_weather_data/?city_name=${encodeURIComponent(cityName)}`)
+ .then(response => {
+ if (!response.ok) {
+ return response.json().then(data => {
+ const message = data.error_message || "City not found";
+ showError(message);
+ throw new Error(message);
+ });
+ }
+ return response.json();
+ })
+ .then(data => {
+ const errorMessage = document.getElementById('error-message');
+ if (errorMessage) {
+ errorMessage.classList.add('hidden');
+ errorMessage.classList.remove('flex');
+ }
+
+ if (data.error_message) {
+ showError(data.error_message);
+ return;
+ }
+
+ form.closest('.input-container').classList.add('opacity-0', 'translate-y-4');
+ setTimeout(() => {
+ form.closest('.input-container').classList.add('hidden');
+ resultContainer.classList.remove('hidden', 'opacity-0', 'translate-y-4');
+ weatherDetails.classList.remove('hidden');
+ resultContainer.classList.add('opacity-100', 'translate-y-0');
+ }, 300);
+
+ renderWeatherDetails(data);
+ })
+ .catch(error => {
+ showError(error.message || "An unexpected error occurred. Please try again.");
+ });
+}
+
+function renderWeatherDetails(data) {
+ document.getElementById('city-name').innerText = data.city_name;
+ document.getElementById('city-time').innerText = data.city_time;
+ document.getElementById('current-temp').innerText = data.temperature;
+ document.getElementById('current-condition').innerText = data.description;
+
+ const hourlyWeatherContainer = document.querySelector('.hourly-weather');
+ hourlyWeatherContainer.innerHTML = '';
+
+ if (data.hourly_forecast && Array.isArray(data.hourly_forecast)) {
+ data.hourly_forecast.forEach(hour => {
+ const weatherIconUrl = `https://openweathermap.org/img/wn/${hour.icon}@2x.png`;
+ const hourCard = document.createElement('div');
+ hourCard.classList.add('hour-card');
+
+ hourCard.innerHTML = `
+ ${hour.time}
+
+ ${hour.temperature}°C
+ `;
+ hourlyWeatherContainer.appendChild(hourCard);
+ });
+ } else {
+ console.error("Hourly forecast data is missing or incorrect.");
+ }
+}
+
+function showError(message) {
+ const errorMessage = document.getElementById('error-message');
+ errorMessage.querySelector('p').textContent = message;
+ console.log("Error message set:", message);
+
+ form.closest('.input-container').classList.add('hidden');
+ weatherDetails.classList.add('hidden');
+
+ resultContainer.classList.remove('hidden');
+ errorMessage.classList.remove('hidden');
+ errorMessage.classList.add('flex');
+ tryAgainButton.classList.remove('hidden');
+}
```
+---
+
# 🎥 video:
-[Screencast 2024-02-28 17:05:46.webm](https://github.com/Ghosts6/weather_news/assets/95994481/4346be05-13fa-4f3f-aaa7-a239d98510a7)
+[weather_news.webm](https://github.com/user-attachments/assets/8574d359-b3ba-4bbf-86c3-27c07d835dcb)
+
+This video showcases the app's functionality, including how users can search for cities and view the weather data in real time.
+
+---
+
+# 🚀 How to Run:
+
+1. **Clone the repository:**
+
+ ```bash
+ git clone https://github.com/Ghosts6/weather_news
+ ```
+2. **Install dependencies:**
+
+Make sure you have a virtual environment set up. If not, you can create one by running:
+ ```bash
+ python -m venv venv
+ source venv/bin/activate # On macOS/Linux
+ venv\Scripts\activate # On Windows
+ ```
+Then, install the required dependencies from the requirements.txt file:
+ ```bash
+ pip install -r requirements.txt
+ ```
+3. **Run database migrations:**
+
+Before running the server, make sure to apply any database migrations:
+ ```bash
+ python manage.py migrate
+ ```
+4. **Collect static files:**
+
+To ensure your static assets (like CSS, JS, images) are served correctly, run:
+ ```bash
+ python manage.py collectstatic
+ ```
+5. **Run the development server:**
+
+Now, you can start the Django development server:
+ ```bash
+ python manage.py runserver
+ ```
+6. **Visit the app:**
+
+Open your browser and visit the app at:
+
+http://127.0.0.1:8000/
+
+
diff --git a/climate/__init__.py b/backend/Tests/models_test.py
similarity index 100%
rename from climate/__init__.py
rename to backend/Tests/models_test.py
diff --git a/backend/Tests/views_test.py b/backend/Tests/views_test.py
new file mode 100644
index 0000000..e9c38e1
--- /dev/null
+++ b/backend/Tests/views_test.py
@@ -0,0 +1,188 @@
+from mock import mock_open
+import pytest
+from django.urls import reverse
+from unittest.mock import patch, MagicMock
+from rest_framework.test import APIClient
+import json
+
+from weather.views import get_timezone_data
+
+@pytest.fixture
+def api_client():
+ return APIClient()
+
+@pytest.mark.django_db
+def test_home_view(api_client):
+ url = reverse('home')
+ response = api_client.get(url)
+
+ assert response.status_code == 200
+ assert 'user_location_summary' in response.context
+ assert 'paris_weather_summary' in response.context
+
+@pytest.mark.django_db
+def test_search_suggestions_valid(api_client):
+ url = reverse('search_suggestions')
+ with patch('builtins.open', mock_open(read_data=json.dumps([
+ {'name': 'Paris'},
+ {'name': 'Parma'},
+ {'name': 'Paradise'}
+ ]))):
+ response = api_client.get(url, {'city_name': 'par'})
+ data = response.json()
+
+ assert data['success'] is True
+ assert 'Paris' in data['suggestions']
+ assert 'Parma' in data['suggestions']
+ assert 'Paradise' in data['suggestions']
+
+@pytest.mark.django_db
+def test_search_suggestions_no_match(api_client):
+ url = reverse('search_suggestions')
+ with patch('builtins.open', mock_open(read_data=json.dumps([
+ {'name': 'Paris'},
+ {'name': 'Parma'},
+ {'name': 'Paradise'}
+ ]))):
+ response = api_client.get(url, {'city_name': 'xyz'})
+ data = response.json()
+
+ assert data['success'] is True
+ assert len(data['suggestions']) == 0
+
+@pytest.mark.django_db
+def test_get_weather_data_valid(api_client):
+ url = reverse('get_weather_data')
+
+ weather_data_mock = {
+ 'cod': 200,
+ 'main': {'temp': 295.15, 'humidity': 80},
+ 'weather': [{'description': 'Clear sky', 'icon': '01d'}],
+ 'wind': {'speed': 5},
+ 'timezone': 3600
+ }
+
+ weatherapi_data_mock = {
+ 'forecast': {
+ 'forecastday': [{
+ 'hour': [{
+ 'time': '2024-11-29 00:00',
+ 'temp_c': 18.0,
+ 'condition': {'text': 'Clear', 'icon': '01d'}
+ }]
+ }]
+ }
+ }
+
+ with patch('requests.get') as mock_get:
+ mock_get.side_effect = [
+ MagicMock(status_code=200, json=MagicMock(return_value=weather_data_mock)),
+ MagicMock(status_code=200, json=MagicMock(return_value=weatherapi_data_mock))
+ ]
+
+ response = api_client.get(url, {'city_name': 'Paris'})
+ data = response.json()
+
+ assert response.status_code == 200
+ assert data['city_name'] == 'Paris'
+ assert 'temperature' in data
+ assert 'description' in data
+ assert len(data['hourly_forecast']) == 1
+ assert data['hourly_forecast'][0]['time'] == '00:00'
+ assert data['hourly_forecast'][0]['temperature'] == 18.0
+
+@pytest.mark.django_db
+def test_get_weather_data_city_not_found(api_client):
+ url = reverse('get_weather_data')
+
+ weather_data_mock = {'cod': '404'}
+
+ with patch('requests.get') as mock_get:
+ mock_get.return_value = MagicMock(status_code=404, json=MagicMock(return_value=weather_data_mock))
+
+ response = api_client.get(url, {'city_name': 'InvalidCity'})
+ data = response.json()
+
+ assert response.status_code == 404
+ assert data['error_message'] == 'City Not Found'
+
+@pytest.mark.django_db
+def test_get_weather_data_missing_city(api_client):
+ url = reverse('get_weather_data')
+
+ response = api_client.get(url, {'city_name': ''})
+ data = response.json()
+
+ assert response.status_code == 400
+ assert data['error_message'] == 'City name is required'
+
+@pytest.mark.django_db
+def test_get_timezone_data_valid():
+ timezone_data_mock = {'timezone': 'Europe/Paris'}
+
+ with patch('requests.get') as mock_get:
+ mock_get.return_value = MagicMock(status_code=200, json=MagicMock(return_value=timezone_data_mock))
+
+ result = get_timezone_data('Paris')
+
+ assert result == 'Europe/Paris'
+
+@pytest.mark.django_db
+def test_get_timezone_data_invalid():
+ timezone_data_mock = {}
+
+ with patch('requests.get') as mock_get:
+ mock_get.return_value = MagicMock(status_code=200, json=MagicMock(return_value=timezone_data_mock))
+
+ result = get_timezone_data('InvalidCity')
+
+ assert result is None
+
+@pytest.mark.django_db
+def test_get_timezone_data_exception():
+ with patch('requests.get') as mock_get:
+ mock_get.side_effect = Exception("API Error")
+
+ result = get_timezone_data('Paris')
+
+ assert result is None
+
+@pytest.mark.django_db
+def test_get_time_zone_valid(api_client):
+ url = reverse('get_time_zone')
+
+ timezone_data_mock = {'timezone': 'Europe/Paris'}
+
+ with patch('requests.get') as mock_get:
+ mock_get.return_value = MagicMock(status_code=200, json=MagicMock(return_value=timezone_data_mock))
+
+ response = api_client.get(url, {'city_name': 'Paris'})
+ data = response.json()
+
+ assert response.status_code == 200
+ assert data['city_name'] == 'Paris'
+ assert data['timezone'] == 'Europe/Paris'
+
+@pytest.mark.django_db
+def test_get_time_zone_city_not_found(api_client):
+ url = reverse('get_time_zone')
+
+ with patch('requests.get') as mock_get:
+ mock_get.return_value = MagicMock(status_code=404, json=MagicMock(return_value={}))
+
+ response = api_client.get(url, {'city_name': 'InvalidCity'})
+ data = response.json()
+
+ assert response.status_code == 404
+ assert data['error_message'] == 'Could not fetch timezone data'
+
+@pytest.mark.django_db
+def test_get_time_zone_missing_city(api_client):
+ url = reverse('get_time_zone')
+
+ response = api_client.get(url, {'city_name': ''})
+ data = response.json()
+
+ assert response.status_code == 400
+ assert data['error_message'] == 'City name is required'
+
diff --git a/climate/Template/404.html b/backend/climate/Template/404.html
similarity index 66%
rename from climate/Template/404.html
rename to backend/climate/Template/404.html
index 0f55489..14b4350 100644
--- a/climate/Template/404.html
+++ b/backend/climate/Template/404.html
@@ -6,16 +6,18 @@
-
+
Error 404
-
-
- Error 404: Page Not Found
+
+
+
+ Error 404: Page Not Found
+
diff --git a/climate/Template/500.html b/backend/climate/Template/500.html
similarity index 66%
rename from climate/Template/500.html
rename to backend/climate/Template/500.html
index 81143d1..a8f3b96 100644
--- a/climate/Template/500.html
+++ b/backend/climate/Template/500.html
@@ -6,16 +6,18 @@
-
+
Error 505
-
-
- Error 500: Unexpected Error :(
+
+
+
+ Error 500: Unexpected Error :(
+
diff --git a/backend/climate/Template/home.html b/backend/climate/Template/home.html
new file mode 100644
index 0000000..c79a034
--- /dev/null
+++ b/backend/climate/Template/home.html
@@ -0,0 +1,133 @@
+{% load static %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Home Page
+
+
+
+
+
+
+
+
+ Weather News
+ {% for weather_news in weather_news_list %}
+ {{ weather_news }}
+ {% endfor %}
+
+
+
+ Search cities
+ Enter the name of your city in the search bar to find out the latest weather updates.
+
+
+ S
+ e
+ a
+ r
+ c
+ h
+
+ C
+ i
+ t
+ y
+
+
+
+
+
+ most viewed location
+ {{ paris_weather_summary }}
+
+
+
+ current location
+ {{ user_location_summary }}
+
+
+
+ Tornado News
+
+ {% for news in tornado_news %}
+
+ {{ news.title }}
+ {{ news.description }}
+ Read more
+
+ {% endfor %}
+
+
+
+
+ Storm News
+
+ {% for news in storm_news %}
+
+ {{ news.title }}
+ {{ news.description }}
+ Read more
+
+ {% endfor %}
+
+
+
+
+ Flood News
+
+ {% for news in flood_news %}
+
+ {{ news.title }}
+ {{ news.description }}
+ Read more
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/climate/Template/weather.html b/backend/climate/Template/weather.html
new file mode 100644
index 0000000..3ec21e3
--- /dev/null
+++ b/backend/climate/Template/weather.html
@@ -0,0 +1,108 @@
+{% load static %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Search result
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Weather Summary
+
+ Local Time:
+ Temperature: °C
+ Condition:
+
+
+
Hourly Forecast
+
+
+
+
+
+
+ Sorry, we couldn't find the weather for that city. Please try again.
+
+
+
+
+
+ Try Again
+
+
+
+
+
+
+ H
+ o
+ m
+ e
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/weather/__init__.py b/backend/climate/__init__.py
similarity index 100%
rename from weather/__init__.py
rename to backend/climate/__init__.py
diff --git a/climate/asgi.py b/backend/climate/asgi.py
similarity index 100%
rename from climate/asgi.py
rename to backend/climate/asgi.py
diff --git a/climate/fixture/city.list.json.txt b/backend/climate/fixture/city.list.json.txt
similarity index 100%
rename from climate/fixture/city.list.json.txt
rename to backend/climate/fixture/city.list.json.txt
diff --git a/climate/settings.py b/backend/climate/settings.py
similarity index 52%
rename from climate/settings.py
rename to backend/climate/settings.py
index 8444f68..f6e9b25 100644
--- a/climate/settings.py
+++ b/backend/climate/settings.py
@@ -5,16 +5,49 @@
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
+# Security config
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = False
+# Uncomment on production
+
+# HTTP Strict Transport Security (HSTS)
+# SECURE_HSTS_SECONDS = 31536000
+# SECURE_HSTS_INCLUDE_SUBDOMAINS = True
+# SECURE_HSTS_PRELOAD = True
+
+# SSL/TLS Settings
+# SECURE_SSL_REDIRECT = True
+# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+
+# Session and Cookie Security
+# SESSION_COOKIE_SECURE = True
+# SESSION_COOKIE_AGE = 1209600
+# SESSION_COOKIE_DOMAIN = '.*'
+# SESSION_COOKIE_SAMESITE = 'Lax'
+# CSRF_COOKIE_SECURE = True
+# CSRF_COOKIE_DOMAIN = '.*'
+# CSRF_COOKIE_SAMESITE = 'Lax'
+
+# Content Security
+# SECURE_CONTENT_TYPE_NOSNIFF = True
+# X_FRAME_OPTIONS = 'DENY'
+
+# CSRF Trusted Origins
+# CSRF_TRUSTED_ORIGINS = [
+# '*',
+# ]
+
+CORS_ALLOWED_ORIGINS = [
+ "http://localhost:home",
+ "http://localhost:weather",
+]
+# Site and Domain Settings
+# SITE_DOMAIN = ''
+
+# Debug and Allowed Hosts
+DEBUG = False
ALLOWED_HOSTS = ['*']
@@ -28,7 +61,6 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'weather',
- 'corsheaders',
]
MIDDLEWARE = [
@@ -40,7 +72,6 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'corsheaders.middleware.CorsMiddleware',
]
ROOT_URLCONF = 'climate.urls'
@@ -127,50 +158,76 @@
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-# Log
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'verbose': {
- 'format': '[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)s] %(message)s',
- 'datefmt': '%Y-%m-%d %H:%M:%S'
- },
- 'simple': {
- 'format': '[%(asctime)s] [%(levelname)s] %(message)s',
- 'datefmt': '%Y-%m-%d %H:%M:%S'
- },
- },
- 'handlers': {
- 'file': {
- 'level': 'INFO', # Set the logging level as needed (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 'class': 'logging.handlers.RotatingFileHandler',
- 'filename': 'climate/log.txt',
- 'maxBytes': 1024 * 1024 * 15, # 15MB
- 'backupCount': 10,
- 'formatter': 'verbose',
- },
- 'console': {
- 'level': 'INFO', # Set the logging level as needed (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 'class': 'logging.StreamHandler',
- 'formatter': 'simple',
- },
- },
- 'loggers': {
- 'django': {
- 'handlers': ['file', 'console'],
- 'level': 'INFO', # Set the logging level as needed (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 'propagate': True,
- },
- 'weather': {
- 'handlers': ['file', 'console'],
- 'level': 'INFO', # Set the logging level as needed (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 'propagate': False,
- },
- },
+import os
+
+# CACHES CONFIGS
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
+ 'LOCATION': os.path.join(BASE_DIR, 'cache'),
+ 'TIMEOUT': 3600,
+ 'OPTIONS': {
+ 'MAX_ENTRIES': 100,
+ 'COMPRESS': True,
+ 'CULL_FREQUENCY': 3,
+ }
+ }
+ # Uncomment the following code for Redis cache in production
+ # 'default': {
+ # 'BACKEND': 'django_redis.cache.RedisCache',
+ # 'LOCATION': 'redis://127.0.0.1:6379/1', # Change for production Redis setup
+ # 'OPTIONS': {
+ # 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
+ # }
+ # }
}
-CORS_ALLOWED_ORIGINS = [
- "http://localhost:home",
- "http://localhost:weather",
-]
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+SESSION_CACHE_ALIAS = 'default'
+
+# Test config
+TEST_RUNNER = 'test_runner.PytestTestRunner'
+# uncommend for debug
+# Log
+# LOGGING = {
+# 'version': 1,
+# 'disable_existing_loggers': False,
+# 'formatters': {
+# 'verbose': {
+# 'format': '[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)s] %(message)s',
+# 'datefmt': '%Y-%m-%d %H:%M:%S'
+# },
+# 'simple': {
+# 'format': '[%(asctime)s] [%(levelname)s] %(message)s',
+# 'datefmt': '%Y-%m-%d %H:%M:%S'
+# },
+# },
+# 'handlers': {
+# 'file': {
+# 'level': 'INFO',
+# 'class': 'logging.handlers.RotatingFileHandler',
+# 'filename': 'climate/Logs/log.txt',
+# 'maxBytes': 1024 * 1024 * 15,
+# 'backupCount': 10,
+# 'formatter': 'verbose',
+# },
+# 'console': {
+# 'level': 'INFO',
+# 'class': 'logging.StreamHandler',
+# 'formatter': 'simple',
+# },
+# },
+# 'loggers': {
+# 'django': {
+# 'handlers': ['file', 'console'],
+# 'level': 'INFO',
+# 'propagate': True,
+# },
+# 'weather': {
+# 'handlers': ['file', 'console'],
+# 'level': 'INFO',
+# 'propagate': False,
+# },
+# },
+# }
+
diff --git a/backend/climate/static/css/output.css b/backend/climate/static/css/output.css
new file mode 100644
index 0000000..4194f3a
--- /dev/null
+++ b/backend/climate/static/css/output.css
@@ -0,0 +1,2256 @@
+*, ::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.15 | 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;
+}
+
+* {
+ scrollbar-color: initial;
+ scrollbar-width: initial;
+}
+
+body {
+ margin: 0px;
+}
+
+@keyframes movingColor {
+ 0% {
+ background-position: 0% 50%;
+ }
+
+ 50% {
+ background-position: 100% 50%;
+ }
+
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+body {
+ animation: movingColor 10s infinite;
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
+ --tw-gradient-from: #e1d0b1 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(225 208 177 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: rgb(163 184 201 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #a3b8c9 var(--tw-gradient-via-position), var(--tw-gradient-to);
+ --tw-gradient-to: #9cd1d1 var(--tw-gradient-to-position);
+ background-attachment: fixed;
+ padding: 1.25rem;
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+body::-webkit-scrollbar-track {
+ background-color: var(--scrollbar-track);
+ border-radius: var(--scrollbar-track-radius);
+}
+
+body::-webkit-scrollbar-track:hover {
+ background-color: var(--scrollbar-track-hover, var(--scrollbar-track));
+}
+
+body::-webkit-scrollbar-track:active {
+ background-color: var(--scrollbar-track-active, var(--scrollbar-track-hover, var(--scrollbar-track)));
+}
+
+body::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-thumb);
+ border-radius: var(--scrollbar-thumb-radius);
+}
+
+body::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-thumb-hover, var(--scrollbar-thumb));
+}
+
+body::-webkit-scrollbar-thumb:active {
+ background-color: var(--scrollbar-thumb-active, var(--scrollbar-thumb-hover, var(--scrollbar-thumb)));
+}
+
+body::-webkit-scrollbar-corner {
+ background-color: var(--scrollbar-corner);
+ border-radius: var(--scrollbar-corner-radius);
+}
+
+body::-webkit-scrollbar-corner:hover {
+ background-color: var(--scrollbar-corner-hover, var(--scrollbar-corner));
+}
+
+body::-webkit-scrollbar-corner:active {
+ background-color: var(--scrollbar-corner-active, var(--scrollbar-corner-hover, var(--scrollbar-corner)));
+}
+
+body {
+ scrollbar-width: auto;
+ scrollbar-color: var(--scrollbar-thumb, initial) var(--scrollbar-track, initial);
+}
+
+body::-webkit-scrollbar {
+ display: block;
+ width: var(--scrollbar-width, 16px);
+ height: var(--scrollbar-height, 16px);
+}
+
+body::-webkit-scrollbar-track {
+ background-color: var(--scrollbar-track);
+ border-radius: var(--scrollbar-track-radius);
+}
+
+body::-webkit-scrollbar-track:hover {
+ background-color: var(--scrollbar-track-hover, var(--scrollbar-track));
+}
+
+body::-webkit-scrollbar-track:active {
+ background-color: var(--scrollbar-track-active, var(--scrollbar-track-hover, var(--scrollbar-track)));
+}
+
+body::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-thumb);
+ border-radius: var(--scrollbar-thumb-radius);
+}
+
+body::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-thumb-hover, var(--scrollbar-thumb));
+}
+
+body::-webkit-scrollbar-thumb:active {
+ background-color: var(--scrollbar-thumb-active, var(--scrollbar-thumb-hover, var(--scrollbar-thumb)));
+}
+
+body::-webkit-scrollbar-corner {
+ background-color: var(--scrollbar-corner);
+ border-radius: var(--scrollbar-corner-radius);
+}
+
+body::-webkit-scrollbar-corner:hover {
+ background-color: var(--scrollbar-corner-hover, var(--scrollbar-corner));
+}
+
+body::-webkit-scrollbar-corner:active {
+ background-color: var(--scrollbar-corner-active, var(--scrollbar-corner-hover, var(--scrollbar-corner)));
+}
+
+body {
+ scrollbar-width: thin;
+ scrollbar-color: var(--scrollbar-thumb, initial) var(--scrollbar-track, initial);
+}
+
+body::-webkit-scrollbar {
+ display: block;
+ width: 8px;
+ height: 8px;
+}
+
+body {
+ --scrollbar-track: #e3e3e3;
+ --scrollbar-thumb: #5a5a5a;
+}
+
+body:is(.dark *) {
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
+ --tw-gradient-from: #292738 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(41 39 56 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: rgb(60 55 80 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #3c3750 var(--tw-gradient-via-position), var(--tw-gradient-to);
+ --tw-gradient-to: #565d7b var(--tw-gradient-to-position);
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+ --scrollbar-track: #3c3750;
+ --scrollbar-thumb: #0be9f0;
+}
+
+@media (min-width: 640px) {
+ body {
+ padding: 1rem;
+ }
+}
+
+@media (min-width: 768px) {
+ body {
+ padding: 1.5rem;
+ }
+}
+
+#particles-js {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ z-index: -1;
+ height: 100%;
+ width: 100%;
+}
+
+.home-main {
+ position: relative;
+ margin-left: auto;
+ margin-right: auto;
+ display: flex;
+ min-height: 100vh;
+ width: calc(100% - 2rem);
+ max-width: 1000px;
+ flex-direction: column;
+ overflow-y: auto;
+ border-radius: 0.5rem;
+ border-width: 4px;
+ border-style: double;
+ --tw-border-opacity: 1;
+ border-color: rgb(127 179 213 / var(--tw-border-opacity, 1));
+ background-color: #d7dbdd70;
+ --tw-bg-opacity: 0.8;
+ padding-bottom: 100px;
+}
+
+.home-main:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(6 80 80 / var(--tw-border-opacity, 1));
+ background-color: rgb(52 73 94 / var(--tw-bg-opacity, 1));
+ --tw-bg-opacity: 0.8;
+}
+
+.search-container {
+ position: relative;
+ margin-left: auto;
+ margin-right: auto;
+ display: flex;
+ height: 80vh;
+ width: calc(100% - 2rem);
+ max-width: 1000px;
+ flex-direction: column;
+ overflow: hidden;
+ border-radius: 0.5rem;
+ border-width: 4px;
+ border-style: double;
+ --tw-border-opacity: 1;
+ border-color: rgb(127 179 213 / var(--tw-border-opacity, 1));
+ background-color: #d7dbdd70;
+ --tw-bg-opacity: 0.8;
+ padding-bottom: 100px;
+}
+
+.search-container:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(6 80 80 / var(--tw-border-opacity, 1));
+ background-color: rgb(52 73 94 / var(--tw-bg-opacity, 1));
+ --tw-bg-opacity: 0.8;
+}
+
+section {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 50px;
+ display: flex;
+ height: auto;
+ width: calc(100% - 2rem);
+ max-width: 900px;
+ flex-direction: column;
+ justify-content: space-between;
+ border-radius: 0.125rem;
+ border-width: 2px;
+ border-style: solid;
+ --tw-border-opacity: 1;
+ border-color: rgb(127 179 213 / var(--tw-border-opacity, 1));
+ background-color: #d7dbdd70;
+ --tw-bg-opacity: 0.8;
+}
+
+section:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(6 80 80 / var(--tw-border-opacity, 1));
+ background-color: rgb(52 73 94 / var(--tw-bg-opacity, 1));
+ --tw-bg-opacity: 0.8;
+}
+
+section img {
+ position: relative;
+ left: 50%;
+ margin-top: 50px;
+ margin-bottom: 25px;
+ height: 400px;
+ width: 600px;
+ --tw-translate-x: -50%;
+ 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));
+ border-radius: 0.5rem;
+ border-width: 1px;
+ border-style: solid;
+ --tw-border-opacity: 1;
+ border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
+ --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);
+}
+
+section img:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
+ --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);
+ --tw-brightness: brightness(.9);
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.title {
+ position: absolute;
+ left: 50%;
+ --tw-translate-x: -50%;
+ 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));
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+ --tw-gradient-from: #9cd1d1 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(156 209 209 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #7fb3d5 var(--tw-gradient-to-position);
+ -webkit-background-clip: text;
+ background-clip: text;
+ text-align: center;
+ font-size: 28px;
+ font-weight: 800;
+ color: transparent;
+ --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1));
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.title:is(.dark *) {
+ --tw-gradient-from: #0B5563 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(11 85 99 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #065050 var(--tw-gradient-to-position);
+}
+
+@media (min-width: 640px) {
+ .title {
+ font-size: 32px;
+ }
+}
+
+@media (min-width: 768px) {
+ .title {
+ font-size: 36px;
+ }
+}
+
+.news-description,
+ .search-description {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 75px;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ text-align: left;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 1.625;
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity, 1));
+ transition-property: all;
+ transition-duration: 300ms;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.news-description:is(.dark *),
+ .search-description:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
+}
+
+@media (min-width: 640px) {
+ .news-description,
+ .search-description {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ font-size: 18px;
+ }
+}
+
+@media (min-width: 768px) {
+ .news-description,
+ .search-description {
+ font-size: 20px;
+ }
+}
+
+.button-search,
+ .button-home {
+ position: relative;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 1.25rem;
+ 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));
+ cursor: pointer;
+ border-radius: 0.5rem;
+ background-image: linear-gradient(to top right, var(--tw-gradient-stops));
+ --tw-gradient-from: #9cd1d1 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(156 209 209 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #7fb3d5 var(--tw-gradient-to-position);
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
+ font-size: 16px;
+ font-weight: 600;
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+ --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);
+ transition-property: all;
+ transition-duration: 300ms;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.button-search:hover,
+ .button-home:hover {
+ --tw-gradient-from: #00ffff var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(0 255 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #008080 var(--tw-gradient-to-position);
+ --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);
+}
+
+.button-search:active,
+ .button-home:active {
+ --tw-scale-x: .95;
+ --tw-scale-y: .95;
+ 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));
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 206 209 / var(--tw-bg-opacity, 1));
+}
+
+.button-search:is(.dark *),
+ .button-home:is(.dark *) {
+ background-image: linear-gradient(to top right, var(--tw-gradient-stops));
+ --tw-gradient-from: #0B5563 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(11 85 99 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #065050 var(--tw-gradient-to-position);
+}
+
+.button-search:hover:is(.dark *),
+ .button-home:hover:is(.dark *) {
+ --tw-gradient-from: #008080 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(0 128 128 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #0be9f0 var(--tw-gradient-to-position);
+}
+
+.button-search:active:is(.dark *),
+ .button-home:active:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 112 112 / var(--tw-bg-opacity, 1));
+}
+
+@media (min-width: 768px) {
+ .button-search,
+ .button-home {
+ font-size: 18px;
+ }
+}
+
+.section-link {
+ font-weight: 700;
+ --tw-text-opacity: 1;
+ color: rgb(0 123 255 / var(--tw-text-opacity, 1));
+ text-decoration-line: underline;
+ 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: 300ms;
+}
+
+.section-link:hover {
+ --tw-text-opacity: 1;
+ color: rgb(0 86 179 / var(--tw-text-opacity, 1));
+}
+
+.section-link:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(0 224 255 / var(--tw-text-opacity, 1));
+}
+
+.section-link:hover:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(0 123 255 / var(--tw-text-opacity, 1));
+}
+
+.news-list {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 4rem;
+ display: flex;
+ width: 100%;
+ max-width: 900px;
+ flex-direction: column;
+ gap: 1.5rem;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+}
+
+@media (min-width: 768px) {
+ .news-list {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+}
+
+@media (min-width: 1024px) {
+ .news-list {
+ padding-left: 2.5rem;
+ padding-right: 2.5rem;
+ }
+}
+
+.news-item {
+ display: flex;
+ flex-direction: column;
+ border-radius: 0.5rem;
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: rgb(224 224 224 / var(--tw-border-opacity, 1));
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+ padding: 1rem;
+ padding-top: 5%;
+ --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);
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.news-item: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);
+}
+
+.news-item:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(52 73 94 / var(--tw-border-opacity, 1));
+ --tw-bg-opacity: 1;
+ background-color: rgb(44 62 80 / var(--tw-bg-opacity, 1));
+}
+
+.news-title {
+ margin-bottom: 0.5rem;
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ font-weight: 700;
+ --tw-text-opacity: 1;
+ color: rgb(51 51 51 / var(--tw-text-opacity, 1));
+}
+
+.news-title:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.news-description {
+ margin-bottom: 1rem;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ line-height: 1.625;
+ --tw-text-opacity: 1;
+ color: rgb(85 85 85 / var(--tw-text-opacity, 1));
+}
+
+.news-description:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(204 204 204 / var(--tw-text-opacity, 1));
+}
+
+.suggestions-container {
+ position: absolute;
+ top: 100%;
+ left: 0px;
+ z-index: 10;
+ max-height: 300px;
+ overflow-y: auto;
+ border-radius: 0.5rem;
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: rgb(204 204 204 / var(--tw-border-opacity, 1));
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+ --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);
+}
+
+.suggestions-container div {
+ cursor: pointer;
+ padding: 0.5rem;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-duration: 300ms;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.suggestions-container div:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(241 241 241 / var(--tw-bg-opacity, 1));
+ --tw-text-opacity: 1;
+ color: rgb(44 62 80 / var(--tw-text-opacity, 1));
+}
+
+.suggestions-container div:active {
+ --tw-bg-opacity: 1;
+ background-color: rgb(226 226 226 / var(--tw-bg-opacity, 1));
+}
+
+.suggestions-container div:focus {
+ --tw-bg-opacity: 1;
+ background-color: rgb(225 245 254 / var(--tw-bg-opacity, 1));
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.weather-details {
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 10vh;
+ display: flex;
+ width: 80%;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 1.25rem;
+}
+
+@media (min-width: 768px) {
+ .weather-details {
+ width: 70%;
+ }
+}
+
+.city-info {
+ margin-bottom: 1.5rem;
+ border-radius: 0.5rem;
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+ --tw-gradient-from: #6a7f8d var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(106 127 141 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ --tw-gradient-to: #4a5f68 var(--tw-gradient-to-position);
+ padding: 1.5rem;
+ text-align: center;
+ --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);
+}
+
+.subtitle {
+ margin-top: 0.5rem;
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ font-weight: 600;
+ --tw-text-opacity: 1;
+ color: rgb(44 62 80 / var(--tw-text-opacity, 1));
+}
+
+.subtitle:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.weather-summary {
+ margin-top: 1rem;
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ --tw-text-opacity: 1;
+ color: rgb(209 209 209 / var(--tw-text-opacity, 1));
+}
+
+.hourly-weather {
+ display: flex;
+ gap: 1.5rem;
+ overflow-x: auto;
+ border-radius: 0.5rem;
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+ padding: 1rem;
+ --tw-text-opacity: 1;
+ color: rgb(44 62 80 / var(--tw-text-opacity, 1));
+ --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);
+}
+
+.hourly-weather::-webkit-scrollbar-track {
+ background-color: var(--scrollbar-track);
+ border-radius: var(--scrollbar-track-radius);
+}
+
+.hourly-weather::-webkit-scrollbar-track:hover {
+ background-color: var(--scrollbar-track-hover, var(--scrollbar-track));
+}
+
+.hourly-weather::-webkit-scrollbar-track:active {
+ background-color: var(--scrollbar-track-active, var(--scrollbar-track-hover, var(--scrollbar-track)));
+}
+
+.hourly-weather::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-thumb);
+ border-radius: var(--scrollbar-thumb-radius);
+}
+
+.hourly-weather::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-thumb-hover, var(--scrollbar-thumb));
+}
+
+.hourly-weather::-webkit-scrollbar-thumb:active {
+ background-color: var(--scrollbar-thumb-active, var(--scrollbar-thumb-hover, var(--scrollbar-thumb)));
+}
+
+.hourly-weather::-webkit-scrollbar-corner {
+ background-color: var(--scrollbar-corner);
+ border-radius: var(--scrollbar-corner-radius);
+}
+
+.hourly-weather::-webkit-scrollbar-corner:hover {
+ background-color: var(--scrollbar-corner-hover, var(--scrollbar-corner));
+}
+
+.hourly-weather::-webkit-scrollbar-corner:active {
+ background-color: var(--scrollbar-corner-active, var(--scrollbar-corner-hover, var(--scrollbar-corner)));
+}
+
+.hourly-weather {
+ scrollbar-width: thin;
+ scrollbar-color: var(--scrollbar-thumb, initial) var(--scrollbar-track, initial);
+}
+
+.hourly-weather::-webkit-scrollbar {
+ display: block;
+ width: 8px;
+ height: 8px;
+}
+
+.hourly-weather {
+ --scrollbar-thumb: #7fb3d5;
+}
+
+.hourly-weather:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(46 58 69 / var(--tw-bg-opacity, 1));
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+ --scrollbar-thumb: #065050;
+}
+
+.pointer-events-none {
+ pointer-events: none;
+}
+
+.static {
+ position: static;
+}
+
+.fixed {
+ position: fixed;
+}
+
+.absolute {
+ position: absolute;
+}
+
+.relative {
+ position: relative;
+}
+
+.bottom-\[-50px\] {
+ bottom: -50px;
+}
+
+.bottom-\[40px\] {
+ bottom: 40px;
+}
+
+.left-0 {
+ left: 0px;
+}
+
+.left-1\/2 {
+ left: 50%;
+}
+
+.right-\[20px\] {
+ right: 20px;
+}
+
+.top-0 {
+ top: 0px;
+}
+
+.top-\[100\%\] {
+ top: 100%;
+}
+
+.top-\[15\%\] {
+ top: 15%;
+}
+
+.top-\[40\%\] {
+ top: 40%;
+}
+
+.top-\[5\%\] {
+ top: 5%;
+}
+
+.top-full {
+ top: 100%;
+}
+
+.z-10 {
+ z-index: 10;
+}
+
+.z-\[999\] {
+ z-index: 999;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem;
+}
+
+.mb-6 {
+ margin-bottom: 1.5rem;
+}
+
+.mt-2 {
+ margin-top: 0.5rem;
+}
+
+.mt-3 {
+ margin-top: 0.75rem;
+}
+
+.mt-4 {
+ margin-top: 1rem;
+}
+
+.mt-6 {
+ margin-top: 1.5rem;
+}
+
+.mt-8 {
+ margin-top: 2rem;
+}
+
+.mt-\[-50px\] {
+ margin-top: -50px;
+}
+
+.inline-block {
+ display: inline-block;
+}
+
+.flex {
+ display: flex;
+}
+
+.hidden {
+ display: none;
+}
+
+.h-14 {
+ height: 3.5rem;
+}
+
+.h-\[24px\] {
+ height: 24px;
+}
+
+.h-\[75vh\] {
+ height: 75vh;
+}
+
+.h-auto {
+ height: auto;
+}
+
+.max-h-\[300px\] {
+ max-height: 300px;
+}
+
+.w-\[24px\] {
+ width: 24px;
+}
+
+.w-\[25\%\] {
+ width: 25%;
+}
+
+.w-\[50px\] {
+ width: 50px;
+}
+
+.w-\[70\%\] {
+ width: 70%;
+}
+
+.w-\[90\%\] {
+ width: 90%;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.max-w-\[90\%\] {
+ max-width: 90%;
+}
+
+.-translate-x-1\/2 {
+ --tw-translate-x: -50%;
+ 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));
+}
+
+.-translate-y-1\/2 {
+ --tw-translate-y: -50%;
+ 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));
+}
+
+.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));
+}
+
+@keyframes bounce-up {
+ 0%, 20%, 50%, 80%, 100% {
+ transform: translateY(0);
+ }
+
+ 40% {
+ transform: translateY(-10px);
+ }
+
+ 60% {
+ transform: translateY(-6px);
+ }
+}
+
+.animate-bounce-up {
+ animation: bounce-up 1.5s infinite;
+}
+
+@keyframes leSnake {
+ 0%, 100% {
+ transform: translateY(0px);
+ }
+
+ 50% {
+ transform: translateY(-5px);
+ }
+}
+
+.animate-leSnake {
+ animation: leSnake 1.5s ease-in-out infinite;
+}
+
+@keyframes lightningText {
+ 0%, 50%, 100% {
+ color: white;
+ }
+
+ 25%, 75% {
+ color: #7fb3d5;
+ }
+}
+
+.animate-lightningText {
+ animation: lightningText 3s infinite;
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-4 {
+ gap: 1rem;
+}
+
+.gap-6 {
+ gap: 1.5rem;
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.overflow-x-auto {
+ overflow-x: auto;
+}
+
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
+.rounded-\[12px\] {
+ border-radius: 12px;
+}
+
+.rounded-lg {
+ border-radius: 0.5rem;
+}
+
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
+.border-\[1px\] {
+ border-width: 1px;
+}
+
+.border-\[2px\] {
+ border-width: 2px;
+}
+
+.border-solid {
+ border-style: solid;
+}
+
+.border-none {
+ border-style: none;
+}
+
+.border-\[\#7fb3d5\] {
+ --tw-border-opacity: 1;
+ border-color: rgb(127 179 213 / var(--tw-border-opacity, 1));
+}
+
+.border-white {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
+}
+
+.bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
+}
+
+.bg-transparent {
+ background-color: transparent;
+}
+
+.bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+}
+
+.bg-gradient-to-tr {
+ background-image: linear-gradient(to top right, var(--tw-gradient-stops));
+}
+
+.from-\[\#6a7f8d\] {
+ --tw-gradient-from: #6a7f8d var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(106 127 141 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-\[\#7fb3d5\] {
+ --tw-gradient-from: #7fb3d5 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(127 179 213 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-\[\#9cd1d1\] {
+ --tw-gradient-from: #9cd1d1 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(156 209 209 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-\[\#e74c3c\] {
+ --tw-gradient-from: #e74c3c var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(231 76 60 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.to-\[\#4a5f68\] {
+ --tw-gradient-to: #4a5f68 var(--tw-gradient-to-position);
+}
+
+.to-\[\#7fb3d5\] {
+ --tw-gradient-to: #7fb3d5 var(--tw-gradient-to-position);
+}
+
+.to-\[\#9cd1d1\] {
+ --tw-gradient-to: #9cd1d1 var(--tw-gradient-to-position);
+}
+
+.to-\[\#c0392b\] {
+ --tw-gradient-to: #c0392b var(--tw-gradient-to-position);
+}
+
+.bg-cover {
+ background-size: cover;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-3 {
+ padding: 0.75rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.p-6 {
+ padding: 1.5rem;
+}
+
+.p-\[12px\] {
+ padding: 12px;
+}
+
+.px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.pt-\[15px\] {
+ padding-top: 15px;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+}
+
+.text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+}
+
+.text-\[28px\] {
+ font-size: 28px;
+}
+
+.text-\[42px\] {
+ font-size: 42px;
+}
+
+.text-\[48px\] {
+ font-size: 48px;
+}
+
+.text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+}
+
+.text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-extrabold {
+ font-weight: 800;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.text-\[\#2c3e50\] {
+ --tw-text-opacity: 1;
+ color: rgb(44 62 80 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#c0392b\] {
+ --tw-text-opacity: 1;
+ color: rgb(192 57 43 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#d1d1d1\] {
+ --tw-text-opacity: 1;
+ color: rgb(209 209 209 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#e74c3c\] {
+ --tw-text-opacity: 1;
+ color: rgb(231 76 60 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#f1f1f1\] {
+ --tw-text-opacity: 1;
+ color: rgb(241 241 241 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#ffffff\] {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / 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);
+}
+
+.transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-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-transform {
+ transition-property: transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.delay-0 {
+ transition-delay: 0s;
+}
+
+.delay-100 {
+ transition-delay: 100ms;
+}
+
+.delay-1000 {
+ transition-delay: 1000ms;
+}
+
+.delay-200 {
+ transition-delay: 200ms;
+}
+
+.delay-300 {
+ transition-delay: 300ms;
+}
+
+.delay-500 {
+ transition-delay: 500ms;
+}
+
+.delay-700 {
+ transition-delay: 700ms;
+}
+
+.duration-300 {
+ transition-duration: 300ms;
+}
+
+.ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.delay-0 {
+ animation-delay: 0s;
+}
+
+.delay-100 {
+ animation-delay: 0.1s;
+}
+
+.delay-200 {
+ animation-delay: 0.2s;
+}
+
+.delay-300 {
+ animation-delay: 0.3s;
+}
+
+.delay-400 {
+ animation-delay: 0.4s;
+}
+
+.delay-500 {
+ animation-delay: 0.5s;
+}
+
+.delay-600 {
+ animation-delay: 0.6s;
+}
+
+.delay-700 {
+ animation-delay: 0.7s;
+}
+
+.delay-800 {
+ animation-delay: 0.8s;
+}
+
+.delay-900 {
+ animation-delay: 0.9s;
+}
+
+.scrollbar-thin::-webkit-scrollbar-track {
+ background-color: var(--scrollbar-track);
+ border-radius: var(--scrollbar-track-radius);
+}
+
+.scrollbar-thin::-webkit-scrollbar-track:hover {
+ background-color: var(--scrollbar-track-hover, var(--scrollbar-track));
+}
+
+.scrollbar-thin::-webkit-scrollbar-track:active {
+ background-color: var(--scrollbar-track-active, var(--scrollbar-track-hover, var(--scrollbar-track)));
+}
+
+.scrollbar-thin::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-thumb);
+ border-radius: var(--scrollbar-thumb-radius);
+}
+
+.scrollbar-thin::-webkit-scrollbar-thumb:hover {
+ background-color: var(--scrollbar-thumb-hover, var(--scrollbar-thumb));
+}
+
+.scrollbar-thin::-webkit-scrollbar-thumb:active {
+ background-color: var(--scrollbar-thumb-active, var(--scrollbar-thumb-hover, var(--scrollbar-thumb)));
+}
+
+.scrollbar-thin::-webkit-scrollbar-corner {
+ background-color: var(--scrollbar-corner);
+ border-radius: var(--scrollbar-corner-radius);
+}
+
+.scrollbar-thin::-webkit-scrollbar-corner:hover {
+ background-color: var(--scrollbar-corner-hover, var(--scrollbar-corner));
+}
+
+.scrollbar-thin::-webkit-scrollbar-corner:active {
+ background-color: var(--scrollbar-corner-active, var(--scrollbar-corner-hover, var(--scrollbar-corner)));
+}
+
+.scrollbar-thin {
+ scrollbar-width: thin;
+ scrollbar-color: var(--scrollbar-thumb, initial) var(--scrollbar-track, initial);
+}
+
+.scrollbar-thin::-webkit-scrollbar {
+ display: block;
+ width: 8px;
+ height: 8px;
+}
+
+.scrollbar-thumb-\[\#7fb3d5\] {
+ --scrollbar-thumb: #7fb3d5 !important;
+}
+
+.text-shadow-error-shadow {
+ text-shadow: 0 10px 250px #00ff00;
+}
+
+body::-webkit-scrollbar {
+ width: 12px;
+}
+
+body::-webkit-scrollbar-track {
+ background: var(--tw-scrollbar-track);
+ border-radius: 10px;
+}
+
+body::-webkit-scrollbar-thumb {
+ background-color: var(--tw-scrollbar-thumb);
+ border-radius: 10px;
+ border: 3px solid var(--tw-scrollbar-track);
+}
+
+body::-webkit-scrollbar-thumb:hover {
+ background-color: #1ad50d;
+}
+
+body.dark::-webkit-scrollbar-thumb {
+ background-color: #0be9f0;
+}
+
+body.dark::-webkit-scrollbar-thumb:hover {
+ background-color: #1ad50d;
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
+.error {
+ background: linear-gradient(
+ to right,green,
+ blue, purple, red,
+ darkblue,darkgreen,darkred
+ );
+ -webkit-background-clip: text;
+ color: transparent;
+ background-position: left bottom;
+ transition: background-position 0.5s ease-in-out;
+ background-size: 300%;
+ animation: shine 12s linear infinite;
+}
+
+.result-container,
+.input-container {
+ transition: all 0.3s ease-in-out;
+}
+
+.opacity-0 {
+ opacity: 0;
+}
+
+.opacity-100 {
+ opacity: 1;
+}
+
+.translate-y-4 {
+ transform: translateY(1rem);
+}
+
+.translate-y-0 {
+ transform: translateY(0);
+}
+
+.placeholder\:text-gray-600::-moz-placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
+}
+
+.placeholder\:text-gray-600::placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
+}
+
+.hover\:scale-\[1\.2\]:hover {
+ --tw-scale-x: 1.2;
+ --tw-scale-y: 1.2;
+ 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\:animate-none:hover {
+ animation: none;
+}
+
+.hover\:from-\[\#00ffff\]:hover {
+ --tw-gradient-from: #00ffff var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(0 255 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.hover\:to-\[\#008080\]:hover {
+ --tw-gradient-to: #008080 var(--tw-gradient-to-position);
+}
+
+.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\:outline-none:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.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-\[\#7fb3d5\]:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(127 179 213 / var(--tw-ring-opacity, 1));
+}
+
+.active\:translate-y-\[2px\]:active {
+ --tw-translate-y: 2px;
+ 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));
+}
+
+.active\:scale-90:active {
+ --tw-scale-x: .9;
+ --tw-scale-y: .9;
+ 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));
+}
+
+.active\:scale-95:active {
+ --tw-scale-x: .95;
+ --tw-scale-y: .95;
+ 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));
+}
+
+.active\:bg-\[\#00ced1\]:active {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 206 209 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:border-\[\#065050\]:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(6 80 80 / var(--tw-border-opacity, 1));
+}
+
+.dark\:border-black:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
+}
+
+.dark\:bg-\[\#2e3a45\]:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(46 58 69 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:bg-gradient-to-tr:is(.dark *) {
+ background-image: linear-gradient(to top right, var(--tw-gradient-stops));
+}
+
+.dark\:from-\[\#0B5563\]:is(.dark *) {
+ --tw-gradient-from: #0B5563 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(11 85 99 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.dark\:to-\[\#065050\]:is(.dark *) {
+ --tw-gradient-to: #065050 var(--tw-gradient-to-position);
+}
+
+.dark\:text-\[\#e74c3c\]:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(231 76 60 / 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-white:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.dark\:shadow-xl:is(.dark *) {
+ --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);
+}
+
+.dark\:brightness-90:is(.dark *) {
+ --tw-brightness: brightness(.9);
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.dark\:scrollbar-thumb-\[\#065050\]:is(.dark *) {
+ --scrollbar-thumb: #065050 !important;
+}
+
+.dark\:hover\:from-\[\#008080\]:hover:is(.dark *) {
+ --tw-gradient-from: #008080 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(0 128 128 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.dark\:hover\:to-\[\#0be9f0\]:hover:is(.dark *) {
+ --tw-gradient-to: #0be9f0 var(--tw-gradient-to-position);
+}
+
+.dark\:focus\:ring-\[\#065050\]:focus:is(.dark *) {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(6 80 80 / var(--tw-ring-opacity, 1));
+}
+
+.dark\:active\:bg-\[\#007070\]:active:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 112 112 / var(--tw-bg-opacity, 1));
+}
+
+@media (min-width: 640px) {
+ .sm\:bottom-\[10px\] {
+ bottom: 10px;
+ }
+
+ .sm\:right-\[5px\] {
+ right: 5px;
+ }
+
+ .sm\:w-\[60px\] {
+ width: 60px;
+ }
+
+ .sm\:p-3 {
+ padding: 0.75rem;
+ }
+
+ .sm\:text-\[32px\] {
+ font-size: 32px;
+ }
+
+ .sm\:text-\[36px\] {
+ font-size: 36px;
+ }
+}
+
+@media (min-width: 768px) {
+ .md\:bottom-\[20px\] {
+ bottom: 20px;
+ }
+
+ .md\:right-\[10px\] {
+ right: 10px;
+ }
+
+ .md\:w-\[70\%\] {
+ width: 70%;
+ }
+
+ .md\:w-\[70px\] {
+ width: 70px;
+ }
+
+ .md\:max-w-\[70\%\] {
+ max-width: 70%;
+ }
+
+ .md\:p-4 {
+ padding: 1rem;
+ }
+
+ .md\:text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ }
+
+ .md\:text-\[36px\] {
+ font-size: 36px;
+ }
+
+ .md\:text-\[40px\] {
+ font-size: 40px;
+ }
+}
\ No newline at end of file
diff --git a/climate/static/fav/android-chrome-192x192.png b/backend/climate/static/fav/android-chrome-192x192.png
similarity index 100%
rename from climate/static/fav/android-chrome-192x192.png
rename to backend/climate/static/fav/android-chrome-192x192.png
diff --git a/climate/static/fav/android-chrome-512x512.png b/backend/climate/static/fav/android-chrome-512x512.png
similarity index 100%
rename from climate/static/fav/android-chrome-512x512.png
rename to backend/climate/static/fav/android-chrome-512x512.png
diff --git a/climate/static/fav/apple-touch-icon.png b/backend/climate/static/fav/apple-touch-icon.png
similarity index 100%
rename from climate/static/fav/apple-touch-icon.png
rename to backend/climate/static/fav/apple-touch-icon.png
diff --git a/climate/static/fav/browserconfig.xml b/backend/climate/static/fav/browserconfig.xml
similarity index 100%
rename from climate/static/fav/browserconfig.xml
rename to backend/climate/static/fav/browserconfig.xml
diff --git a/climate/static/fav/favicon-16x16.png b/backend/climate/static/fav/favicon-16x16.png
similarity index 100%
rename from climate/static/fav/favicon-16x16.png
rename to backend/climate/static/fav/favicon-16x16.png
diff --git a/climate/static/fav/favicon-32x32.png b/backend/climate/static/fav/favicon-32x32.png
similarity index 100%
rename from climate/static/fav/favicon-32x32.png
rename to backend/climate/static/fav/favicon-32x32.png
diff --git a/climate/static/fav/favicon.ico b/backend/climate/static/fav/favicon.ico
similarity index 100%
rename from climate/static/fav/favicon.ico
rename to backend/climate/static/fav/favicon.ico
diff --git a/climate/static/fav/mstile-150x150.png b/backend/climate/static/fav/mstile-150x150.png
similarity index 100%
rename from climate/static/fav/mstile-150x150.png
rename to backend/climate/static/fav/mstile-150x150.png
diff --git a/climate/static/fav/safari-pinned-tab.svg b/backend/climate/static/fav/safari-pinned-tab.svg
similarity index 100%
rename from climate/static/fav/safari-pinned-tab.svg
rename to backend/climate/static/fav/safari-pinned-tab.svg
diff --git a/climate/static/fav/site.webmanifest b/backend/climate/static/fav/site.webmanifest
similarity index 100%
rename from climate/static/fav/site.webmanifest
rename to backend/climate/static/fav/site.webmanifest
diff --git a/climate/static/fav/tag.txt b/backend/climate/static/fav/tag.txt
similarity index 100%
rename from climate/static/fav/tag.txt
rename to backend/climate/static/fav/tag.txt
diff --git a/backend/climate/static/img/city_not_found.png b/backend/climate/static/img/city_not_found.png
new file mode 100644
index 0000000..0df5935
Binary files /dev/null and b/backend/climate/static/img/city_not_found.png differ
diff --git a/climate/static/img/flood.jpg b/backend/climate/static/img/flood.jpg
similarity index 100%
rename from climate/static/img/flood.jpg
rename to backend/climate/static/img/flood.jpg
diff --git a/climate/static/img/local.webp b/backend/climate/static/img/local.webp
similarity index 100%
rename from climate/static/img/local.webp
rename to backend/climate/static/img/local.webp
diff --git a/climate/static/img/logo.png b/backend/climate/static/img/logo.png
similarity index 100%
rename from climate/static/img/logo.png
rename to backend/climate/static/img/logo.png
diff --git a/climate/static/img/mostviewd.png b/backend/climate/static/img/mostviewd.png
similarity index 100%
rename from climate/static/img/mostviewd.png
rename to backend/climate/static/img/mostviewd.png
diff --git a/backend/climate/static/img/rain.png b/backend/climate/static/img/rain.png
new file mode 100644
index 0000000..8345fe6
Binary files /dev/null and b/backend/climate/static/img/rain.png differ
diff --git a/backend/climate/static/img/search.png b/backend/climate/static/img/search.png
new file mode 100644
index 0000000..37e65c1
Binary files /dev/null and b/backend/climate/static/img/search.png differ
diff --git a/backend/climate/static/img/snow.png b/backend/climate/static/img/snow.png
new file mode 100644
index 0000000..3285c69
Binary files /dev/null and b/backend/climate/static/img/snow.png differ
diff --git a/climate/static/img/storm.jpg b/backend/climate/static/img/storm.jpg
similarity index 100%
rename from climate/static/img/storm.jpg
rename to backend/climate/static/img/storm.jpg
diff --git a/climate/static/img/tornado.jpg b/backend/climate/static/img/tornado.jpg
similarity index 100%
rename from climate/static/img/tornado.jpg
rename to backend/climate/static/img/tornado.jpg
diff --git a/backend/climate/static/img/upArrow.svg b/backend/climate/static/img/upArrow.svg
new file mode 100644
index 0000000..ef82cc5
--- /dev/null
+++ b/backend/climate/static/img/upArrow.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/climate/static/img/weather-search.jpg b/backend/climate/static/img/weather-search.jpg
new file mode 100644
index 0000000..815854e
Binary files /dev/null and b/backend/climate/static/img/weather-search.jpg differ
diff --git a/climate/static/img/weather.avif b/backend/climate/static/img/weather.avif
similarity index 100%
rename from climate/static/img/weather.avif
rename to backend/climate/static/img/weather.avif
diff --git a/backend/climate/static/img/wind.png b/backend/climate/static/img/wind.png
new file mode 100644
index 0000000..3080c7b
Binary files /dev/null and b/backend/climate/static/img/wind.png differ
diff --git a/climate/static/js/404.js b/backend/climate/static/js/404.js
similarity index 100%
rename from climate/static/js/404.js
rename to backend/climate/static/js/404.js
diff --git a/backend/climate/static/js/home.js b/backend/climate/static/js/home.js
new file mode 100644
index 0000000..73a4312
--- /dev/null
+++ b/backend/climate/static/js/home.js
@@ -0,0 +1,42 @@
+document.addEventListener('DOMContentLoaded', function () {
+ getLocation();
+ const homeButton = document.getElementById('searchButton');
+ homeButton.addEventListener('click', function () {
+ window.location.href = redirect;
+ });
+});
+
+
+function getLocation() {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(showPosition, showError);
+ } else {
+ console.error("Geolocation is not supported by this browser.");
+ }
+}
+
+function showPosition(position) {
+ fetch(`/get_user_location/${position.coords.latitude}/${position.coords.longitude}/`)
+ .then(response => response.json())
+ .then(data => {
+ console.log(data);
+ })
+ .catch(error => console.error('Error:', error));
+}
+
+function showError(error) {
+ switch (error.code) {
+ case error.PERMISSION_DENIED:
+ alert("User denied the request for Geolocation.");
+ break;
+ case error.POSITION_UNAVAILABLE:
+ alert("Location information is unavailable.");
+ break;
+ case error.TIMEOUT:
+ alert("The request to get user location timed out.");
+ break;
+ case error.UNKNOWN_ERROR:
+ alert("An unknown error occurred.");
+ break;
+ }
+}
\ No newline at end of file
diff --git a/backend/climate/static/js/template.js b/backend/climate/static/js/template.js
new file mode 100644
index 0000000..e4ec07b
--- /dev/null
+++ b/backend/climate/static/js/template.js
@@ -0,0 +1,144 @@
+// Particles config
+function getRandomMode() {
+ const modes = ['snowy', 'rainy', 'windy'];
+ const randomIndex = Math.floor(Math.random() * modes.length);
+ return modes[randomIndex];
+}
+
+function getImageForMode(mode) {
+ switch (mode) {
+ case 'snowy':
+ return '/static/img/snow.png';
+ case 'rainy':
+ return '/static/img/rain.png';
+ case 'windy':
+ return '/static/img/wind.png';
+ default:
+ return '/static/img/snow.png';
+ }
+}
+
+const mode = getRandomMode();
+const particleImage = getImageForMode(mode);
+
+particlesJS("particles-js", {
+ particles: {
+ number: {
+ value: 150,
+ density: {
+ enable: true,
+ value_area: 800
+ }
+ },
+ color: {
+ value: "#ffffff"
+ },
+ shape: {
+ type: "image",
+ stroke: {
+ width: 0,
+ color: "#000000"
+ },
+ image: {
+ src: particleImage,
+ width: 40,
+ height: 40
+ }
+ },
+ opacity: {
+ value: 0.7,
+ random: true,
+ anim: {
+ enable: true,
+ speed: 1,
+ opacity_min: 0,
+ sync: false
+ }
+ },
+ size: {
+ value: 10,
+ random: true,
+ anim: {
+ enable: true,
+ speed: 3,
+ size_min: 0,
+ sync: false
+ }
+ },
+ line_linked: {
+ enable: false
+ },
+ move: {
+ enable: true,
+ speed: 1,
+ direction: "bottom",
+ random: false,
+ straight: false,
+ out_mode: "out",
+ bounce: false,
+ attract: {
+ enable: false,
+ rotateX: 600,
+ rotateY: 1200
+ }
+ }
+ },
+ interactivity: {
+ detect_on: "canvas",
+ events: {
+ onhover: {
+ enable: false
+ },
+ onclick: {
+ enable: false
+ },
+ resize: true
+ }
+ },
+ retina_detect: true
+});
+// Scroll to top
+document.addEventListener('DOMContentLoaded', () => {
+ const scrollToTopButton = document.getElementById('scrollToTopButton');
+
+ window.addEventListener('scroll', () => {
+ scrollToTopButton.style.display = window.scrollY > 100 ? 'block' : 'none';
+ });
+
+ scrollToTopButton.addEventListener('click', () => {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ });
+});
+
+// Toggle Light mode
+function toggleDarkMode() {
+ const html = document.documentElement;
+ const darkModeIcon = document.getElementById('dark-mode-icon');
+
+ html.classList.toggle('dark');
+
+ darkModeIcon.classList.toggle('fa-moon');
+ darkModeIcon.classList.toggle('fa-sun');
+
+ if (html.classList.contains('dark')) {
+ localStorage.setItem('theme', 'dark');
+ } else {
+ localStorage.setItem('theme', 'light');
+ }
+}
+
+function applySavedTheme() {
+ const savedTheme = localStorage.getItem('theme');
+
+ if (savedTheme === 'dark') {
+ document.documentElement.classList.add('dark');
+ document.getElementById('dark-mode-icon').classList.add('fa-sun');
+ document.getElementById('dark-mode-icon').classList.remove('fa-moon');
+ } else {
+ document.documentElement.classList.remove('dark');
+ document.getElementById('dark-mode-icon').classList.add('fa-moon');
+ document.getElementById('dark-mode-icon').classList.remove('fa-sun');
+ }
+}
+
+applySavedTheme();
\ No newline at end of file
diff --git a/backend/climate/static/js/weather.js b/backend/climate/static/js/weather.js
new file mode 100644
index 0000000..aa4e9ef
--- /dev/null
+++ b/backend/climate/static/js/weather.js
@@ -0,0 +1,191 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const homeButton = document.getElementById('homeButton');
+ const form = document.getElementById('weather-form');
+ const searchInput = document.getElementById('search');
+ const resultContainer = document.getElementById('result-container');
+ const weatherDetails = document.getElementById('weather-details');
+ const errorMessage = document.getElementById('error-message');
+ const tryAgainButton = document.getElementById('try-again-button');
+ const suggestionsContainer = document.getElementById('suggestions-container');
+ const validCityNamePattern = /^[a-zA-Z\s\-]+$/;
+
+ let currentIndex = -1;
+ let suggestions = [];
+
+ homeButton.addEventListener('click', function () {
+ window.location.href = redirect;
+ });
+
+ form.addEventListener('submit', function (event) {
+ event.preventDefault();
+ const cityName = searchInput.value.trim();
+
+ if (!cityName) {
+ Swal.fire({
+ icon: 'warning',
+ title: 'Input Required',
+ text: 'Please enter a city name before searching!',
+ });
+ return;
+ }
+
+ if (!validCityNamePattern.test(cityName)) {
+ Swal.fire({
+ icon: 'error',
+ title: 'Invalid Input',
+ text: 'City name must only contain letters, spaces, or hyphens.',
+ });
+ return;
+ }
+
+ fetchWeather(cityName);
+ });
+
+ tryAgainButton.addEventListener('click', function () {
+ resultContainer.classList.add('opacity-0', 'translate-y-4');
+ setTimeout(() => {
+ resultContainer.classList.add('hidden');
+ form.closest('.input-container').classList.remove('hidden', 'opacity-0', 'translate-y-4');
+ form.closest('.input-container').classList.add('opacity-100', 'translate-y-0');
+ }, 300);
+ });
+
+ function fetchWeather(cityName) {
+ fetch(`/get_weather_data/?city_name=${encodeURIComponent(cityName)}`)
+ .then(response => {
+ if (!response.ok) {
+ return response.json().then(data => {
+ const message = data.error_message || "City not found";
+ showError(message);
+ throw new Error(message);
+ });
+ }
+ return response.json();
+ })
+ .then(data => {
+ const errorMessage = document.getElementById('error-message');
+ if (errorMessage) {
+ errorMessage.classList.add('hidden');
+ errorMessage.classList.remove('flex');
+ }
+
+ if (data.error_message) {
+ showError(data.error_message);
+ return;
+ }
+
+ form.closest('.input-container').classList.add('opacity-0', 'translate-y-4');
+ setTimeout(() => {
+ form.closest('.input-container').classList.add('hidden');
+ resultContainer.classList.remove('hidden', 'opacity-0', 'translate-y-4');
+ weatherDetails.classList.remove('hidden');
+ resultContainer.classList.add('opacity-100', 'translate-y-0');
+ }, 300);
+
+ renderWeatherDetails(data);
+ })
+ .catch(error => {
+ showError(error.message || "An unexpected error occurred. Please try again.");
+ });
+ }
+
+ function renderWeatherDetails(data) {
+ document.getElementById('city-name').innerText = data.city_name;
+ document.getElementById('city-time').innerText = data.city_time;
+ document.getElementById('current-temp').innerText = data.temperature;
+ document.getElementById('current-condition').innerText = data.description;
+
+ const hourlyWeatherContainer = document.querySelector('.hourly-weather');
+ hourlyWeatherContainer.innerHTML = '';
+
+ if (data.hourly_forecast && Array.isArray(data.hourly_forecast)) {
+ data.hourly_forecast.forEach(hour => {
+ const weatherIconUrl = `https://openweathermap.org/img/wn/${hour.icon}@2x.png`;
+ const hourCard = document.createElement('div');
+ hourCard.classList.add('hour-card');
+
+ hourCard.innerHTML = `
+ ${hour.time}
+
+ ${hour.temperature}°C
+ `;
+ hourlyWeatherContainer.appendChild(hourCard);
+ });
+ } else {
+ console.error("Hourly forecast data is missing or incorrect.");
+ }
+ }
+
+ function showError(message) {
+ const errorMessage = document.getElementById('error-message');
+ errorMessage.querySelector('p').textContent = message;
+ console.log("Error message set:", message);
+
+ form.closest('.input-container').classList.add('hidden');
+ weatherDetails.classList.add('hidden');
+
+ resultContainer.classList.remove('hidden');
+ errorMessage.classList.remove('hidden');
+ errorMessage.classList.add('flex');
+ tryAgainButton.classList.remove('hidden');
+ }
+
+ searchInput.addEventListener('input', function () {
+ const query = searchInput.value.trim();
+ suggestionsContainer.innerHTML = '';
+ currentIndex = -1;
+
+ if (query.length >= 3) {
+ fetch(`/search_suggestions/?city_name=${encodeURIComponent(query)}`)
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ suggestions = data.suggestions;
+ suggestions.forEach((city, index) => {
+ const suggestion = document.createElement('div');
+ suggestion.textContent = city;
+ suggestion.className = "cursor-pointer p-2 transition-all hover:bg-[#f1f1f1] dark:hover:bg-[#3c3c3c] hover:text-[#2c3e50]";
+ suggestion.addEventListener('click', () => {
+ searchInput.value = city;
+ suggestionsContainer.innerHTML = '';
+ suggestionsContainer.classList.add('hidden');
+ });
+
+ suggestion.addEventListener('mouseenter', () => {
+ currentIndex = index;
+ updateSuggestionHighlight();
+ });
+
+ suggestionsContainer.appendChild(suggestion);
+ });
+ suggestionsContainer.classList.remove('hidden');
+ }
+ });
+ }
+ });
+
+ searchInput.addEventListener('keydown', (event) => {
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
+ event.preventDefault();
+ if (event.key === 'ArrowDown' && currentIndex < suggestions.length - 1) {
+ currentIndex++;
+ } else if (event.key === 'ArrowUp' && currentIndex > 0) {
+ currentIndex--;
+ }
+ updateSuggestionHighlight();
+ }
+ });
+
+ function updateSuggestionHighlight() {
+ const items = suggestionsContainer.querySelectorAll('div');
+ items.forEach((item, index) => {
+ if (index === currentIndex) {
+ item.style.backgroundColor = '#e1f5fe';
+ item.style.color = '#2c3e50';
+ } else {
+ item.style.backgroundColor = '';
+ item.style.color = '';
+ }
+ });
+ }
+});
diff --git a/climate/urls.py b/backend/climate/urls.py
similarity index 100%
rename from climate/urls.py
rename to backend/climate/urls.py
diff --git a/climate/views.py b/backend/climate/views.py
similarity index 100%
rename from climate/views.py
rename to backend/climate/views.py
diff --git a/climate/wsgi.py b/backend/climate/wsgi.py
similarity index 100%
rename from climate/wsgi.py
rename to backend/climate/wsgi.py
diff --git a/manage.py b/backend/manage.py
similarity index 100%
rename from manage.py
rename to backend/manage.py
diff --git a/backend/pytest.ini b/backend/pytest.ini
new file mode 100644
index 0000000..2b2bb18
--- /dev/null
+++ b/backend/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+DJANGO_SETTINGS_MODULE = climate.settings
+python_files = *_test.py
\ No newline at end of file
diff --git a/backend/test_runner.py b/backend/test_runner.py
new file mode 100644
index 0000000..1b4e16b
--- /dev/null
+++ b/backend/test_runner.py
@@ -0,0 +1,15 @@
+import os
+import sys
+import pytest
+from django.test.runner import DiscoverRunner
+
+class PytestTestRunner(DiscoverRunner):
+ def run_tests(self, test_labels, extra_tests=None, **kwargs):
+ os.environ['DJANGO_SETTINGS_MODULE'] = 'climate.settings'
+ pytest_args = [
+ 'Tests',
+ '--ds=climate.settings',
+ ]
+ if test_labels:
+ pytest_args.extend(test_labels)
+ return pytest.main(pytest_args)
\ No newline at end of file
diff --git a/weather/migrations/__init__.py b/backend/weather/__init__.py
similarity index 100%
rename from weather/migrations/__init__.py
rename to backend/weather/__init__.py
diff --git a/weather/admin.py b/backend/weather/admin.py
similarity index 100%
rename from weather/admin.py
rename to backend/weather/admin.py
diff --git a/weather/apps.py b/backend/weather/apps.py
similarity index 100%
rename from weather/apps.py
rename to backend/weather/apps.py
diff --git a/climate/log.txt b/backend/weather/management/__init__.py
similarity index 100%
rename from climate/log.txt
rename to backend/weather/management/__init__.py
diff --git a/backend/weather/management/commands/__init__.py b/backend/weather/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/weather/management/commands/build_react_app.py b/backend/weather/management/commands/build_react_app.py
new file mode 100644
index 0000000..fde605e
--- /dev/null
+++ b/backend/weather/management/commands/build_react_app.py
@@ -0,0 +1,49 @@
+import os
+import subprocess
+import shutil
+from django.core.management.base import BaseCommand
+from django.core.management import call_command
+
+class Command(BaseCommand):
+ help = 'Builds React app, copies build files to Django static directory, and runs collectstatic.'
+
+ def handle(self, *args, **kwargs):
+ project_root_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../..'))
+
+ frontend_dir = os.path.join(project_root_dir, 'frontend')
+
+ if not os.path.isdir(frontend_dir):
+ self.stdout.write(self.style.ERROR(f"Frontend directory not found at {frontend_dir}"))
+ return
+
+ self.stdout.write(self.style.WARNING('Building React app...'))
+ try:
+ subprocess.run(['npm', 'run', 'build'], cwd=frontend_dir, check=True)
+ self.stdout.write(self.style.SUCCESS('React app built successfully.'))
+ except subprocess.CalledProcessError as e:
+ self.stdout.write(self.style.ERROR(f"Error occurred while building React app: {e}"))
+ return
+
+ build_dir = os.path.join(frontend_dir, 'build')
+ static_frontend_dir = os.path.join(project_root_dir, 'backend/climate/static/frontend')
+
+ if not os.path.isdir(build_dir):
+ self.stdout.write(self.style.ERROR(f"Build directory not found at {build_dir}"))
+ return
+
+ self.stdout.write(self.style.WARNING('Copying build files to Django static directory...'))
+ try:
+ shutil.copytree(build_dir, static_frontend_dir, dirs_exist_ok=True)
+ self.stdout.write(self.style.SUCCESS('Build files copied successfully.'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f"Error occurred while copying build files: {e}"))
+ return
+
+ self.stdout.write(self.style.WARNING('Running collectstatic...'))
+ try:
+ call_command('collectstatic', '--noinput')
+ self.stdout.write(self.style.SUCCESS('collectstatic completed successfully.'))
+ except Exception as e:
+ self.stdout.write(self.style.ERROR(f"Error occurred during collectstatic: {e}"))
+ return
+
diff --git a/backend/weather/migrations/__init__.py b/backend/weather/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/weather/models.py b/backend/weather/models.py
similarity index 100%
rename from weather/models.py
rename to backend/weather/models.py
diff --git a/weather/tests.py b/backend/weather/tests.py
similarity index 100%
rename from weather/tests.py
rename to backend/weather/tests.py
diff --git a/weather/urls.py b/backend/weather/urls.py
similarity index 63%
rename from weather/urls.py
rename to backend/weather/urls.py
index 94bb609..de24432 100644
--- a/weather/urls.py
+++ b/backend/weather/urls.py
@@ -6,9 +6,10 @@
urlpatterns = [
path('', views.home, name='home'),
- path('home/', views.home, name="home"),
- path('weather/', views.weather, name="weather"),
- path('weather/home/', views.home , name="home"),
+ path('home/', views.home, name='home'),
+ path('weather/', views.weather, name='weather'),
+ path('get_weather_data/', views.get_weather_data, name='get_weather_data'),
+ path('get_time_zone/', views.get_time_zone , name='get_time_zone'),
path('get_user_location///', views.get_user_location, name='get_user_location'),
path('search_suggestions/', views.search_suggestions, name='search_suggestions'),
]
\ No newline at end of file
diff --git a/backend/weather/views.py b/backend/weather/views.py
new file mode 100644
index 0000000..e8001c6
--- /dev/null
+++ b/backend/weather/views.py
@@ -0,0 +1,310 @@
+from django.views.decorators.cache import cache_page
+from datetime import datetime, timedelta
+from django.shortcuts import render, redirect
+from django.http import JsonResponse
+from geopy.geocoders import Nominatim
+from geopy.exc import GeocoderUnavailable
+from dotenv import load_dotenv
+import json
+import pytz
+import requests
+import time
+import os
+
+# Load environment variables
+load_dotenv()
+API_KEY = os.getenv('API_KEY')
+NEWS_API_KEY = os.getenv('NEWS_API_KEY')
+WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
+WEATHER_API_KEY_2 = os.getenv('WEATHER_API_KEY_2')
+
+# variables
+MAX_RETRIES = 3
+TIMEOUT = 10
+
+# Pre define url
+TIMEZONE_API_URL = 'https://geocode.xyz/{city_name}?json=1&timezone=1'
+
+### HELPER FUNCTIONS ###
+
+def kelvin_to_celsius(kelvin):
+ """Convert temperature from Kelvin to Celsius."""
+ return kelvin - 273.15
+
+def get_news(query, count=5):
+ news_url = f"https://newsapi.org/v2/everything?q={query}&apiKey={NEWS_API_KEY}&pageSize={count}"
+ try:
+ response = requests.get(news_url)
+
+ if response.status_code == 200:
+ news_data = response.json()
+ articles = news_data.get('articles', [])
+
+ news_list = [
+ {
+ 'title': article.get('title', 'N/A'),
+ 'description': article.get('description', 'N/A'),
+ 'url': article.get('url', '#')
+ }
+ for article in articles
+ ]
+ return news_list
+
+ elif response.status_code == 401:
+ print("Invalid API Key for News API.")
+ else:
+ print(f"Error fetching news: {response.status_code} - {response.text}")
+ except Exception as e:
+ print(f"Error fetching news: {e}")
+
+ return []
+
+def fetch_user_ip():
+ try:
+ response = requests.get('https://httpbin.org/ip')
+ if response.status_code == 200:
+ return response.json().get('origin', '')
+ except Exception as e:
+ print(f"Error fetching IP: {e}")
+ return None
+
+def get_user_location(request, latitude, longitude):
+ return JsonResponse({"status": "success", "message": "Location received successfully."})
+
+def get_location_from_ip(user_ip):
+ try:
+ ip_api_url = f"https://ipapi.co/{user_ip}/json/"
+ response = requests.get(ip_api_url)
+
+ if response.status_code == 200:
+ data = response.json()
+ return f"{data.get('city', 'Unknown City')}, {data.get('region', 'Unknown Region')}, {data.get('country_name', 'Unknown Country')}"
+ elif response.status_code == 429:
+ print("Rate limit exceeded. Falling back to default location.")
+ return "Fallback City, Fallback Region, Fallback Country"
+ else:
+ print(f"Failed IP location fetch. Status Code: {response.status_code}")
+ except Exception as e:
+ print(f"Error during IP geolocation: {e}")
+ return "Location Unavailable"
+
+def get_weather_data(request):
+ city_name = request.GET.get('city_name', '').strip()
+
+ if city_name:
+ weather_url = f'http://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={API_KEY}'
+ weatherapi_url = f'https://api.weatherapi.com/v1/forecast.json?key={WEATHER_API_KEY_2}&q={city_name}&hours=12'
+
+ def fetch_with_retry(url, retries=MAX_RETRIES):
+ for attempt in range(retries):
+ try:
+ response = requests.get(url, timeout=TIMEOUT)
+ response.raise_for_status()
+ return response
+ except requests.RequestException as e:
+ if attempt < retries - 1:
+ time.sleep(2 ** attempt)
+ else:
+ raise e
+
+ try:
+ weather_response = fetch_with_retry(weather_url)
+ weather_data = weather_response.json()
+
+ if weather_data.get('cod') != 200:
+ return JsonResponse({'error_message': 'City Not Found'}, status=404)
+
+ temperature = kelvin_to_celsius(weather_data.get('main', {}).get('temp', 0))
+ description = weather_data.get('weather', [{}])[0].get('description', '')
+ icon = weather_data.get('weather', [{}])[0].get('icon', '')
+ wind_speed = weather_data.get('wind', {}).get('speed', 0)
+ humidity = weather_data.get('main', {}).get('humidity', 0)
+
+ city_timezone = weather_data.get('timezone', 0)
+ utc_time = datetime.utcnow() + timedelta(seconds=city_timezone)
+ city_time = utc_time.strftime('%Y-%m-%d %H:%M:%S')
+
+ weatherapi_response = fetch_with_retry(weatherapi_url)
+ weatherapi_data = weatherapi_response.json()
+
+ hourly_forecast = []
+ if 'forecast' in weatherapi_data and 'forecastday' in weatherapi_data['forecast']:
+ for hour in weatherapi_data['forecast']['forecastday'][0]['hour'][:12]:
+ hour_data = {
+ 'time': hour['time'].split(' ')[1],
+ 'temperature': hour['temp_c'],
+ 'description': hour['condition']['text'],
+ 'icon': hour['condition']['icon'],
+ }
+ hourly_forecast.append(hour_data)
+
+ response_data = {
+ 'city_name': city_name,
+ 'temperature': temperature,
+ 'description': description,
+ 'icon': icon,
+ 'city_time': city_time,
+ 'wind_speed': wind_speed,
+ 'humidity': humidity,
+ 'timezone': city_timezone,
+ 'hourly_forecast': hourly_forecast,
+ }
+ return JsonResponse(response_data)
+
+ except Exception as e:
+ return JsonResponse({'error_message': f'Error fetching data: {str(e)}'}, status=500)
+
+ return JsonResponse({'error_message': 'City name is required'}, status=400)
+
+def get_timezone_data(city_name):
+ try:
+ response = requests.get(TIMEZONE_API_URL.format(city_name=city_name))
+ response_data = response.json()
+
+ if response_data.get('timezone'):
+ return response_data['timezone']
+ else:
+ return None
+ except Exception as e:
+ return None
+
+def get_time_zone(request):
+ city_name = request.GET.get('city_name', '').strip()
+
+ if city_name:
+ timezone = get_timezone_data(city_name)
+
+ if timezone:
+ return JsonResponse({'city_name': city_name, 'timezone': timezone})
+ else:
+ return JsonResponse({'error_message': 'Could not fetch timezone data'}, status=404)
+
+ return JsonResponse({'error_message': 'City name is required'}, status=400)
+
+def fetch_weather_by_coordinates(lat, lon):
+ try:
+ url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API_KEY}"
+ response = requests.get(url)
+
+ if response.status_code == 200:
+ return response.json()
+ elif response.status_code == 401:
+ print("Invalid API Key for OpenWeather API.")
+ else:
+ print(f"Error fetching weather: {response.status_code} - {response.text}")
+ except Exception as e:
+ print(f"Error fetching weather by coordinates: {e}")
+ return None
+
+def format_weather_data(weather_data):
+ try:
+ city_name = weather_data['name']
+ description = weather_data['weather'][0]['description']
+ temperature = kelvin_to_celsius(weather_data['main']['temp'])
+ feels_like = kelvin_to_celsius(weather_data['main']['feels_like'])
+ humidity = weather_data['main']['humidity']
+ wind_speed = weather_data['wind']['speed']
+
+ return f"""
+ ## Current Weather in {city_name}:
+
+ * Description: {description}
+ * Temperature: {temperature:.2f}°C
+ * Feels Like: {feels_like:.2f}°C
+ * Humidity: {humidity}%
+ * Wind Speed: {wind_speed:.2f} m/s
+ """
+ except KeyError as e:
+ print(f"Error formatting weather data: {e}")
+ return "Incomplete weather data."
+
+### VIEWS ###
+@cache_page(60 * 15)
+def home(request):
+ default_lat = 40.7128
+ default_lon = -74.0060
+ user_location_summary = "Displaying default weather for New York."
+
+ try:
+ default_weather_data = fetch_weather_by_coordinates(default_lat, default_lon)
+ if default_weather_data:
+ user_location_summary = format_weather_data(default_weather_data)
+ except Exception as e:
+ print(f"Error fetching default weather for New York: {e}")
+
+ user_ip = fetch_user_ip()
+ if user_ip:
+ location_string = get_location_from_ip(user_ip)
+ if location_string:
+ try:
+ print(f"User IP location: {location_string}")
+ location_details = location_string.split(',')[0]
+ geolocation_data = fetch_weather_by_coordinates(default_lat, default_lon)
+ if geolocation_data:
+ weather_data = fetch_weather_by_coordinates(
+ geolocation_data['coord']['lat'],
+ geolocation_data['coord']['lon']
+ )
+ if weather_data:
+ user_location_summary = format_weather_data(weather_data)
+ else:
+ user_location_summary = "Failed to fetch precise weather data."
+ else:
+ user_location_summary = "Fallback to default weather as geolocation failed."
+ except Exception as e:
+ print(f"Geolocation error: {e}")
+ else:
+ print("Location unavailable from IP, using default weather.")
+ else:
+ print("Failed to fetch user IP. Using New York as default.")
+
+ paris_weather_summary = "Failed to fetch weather data for Paris."
+ try:
+ paris_weather_data = fetch_weather_by_coordinates(48.8566, 2.3522)
+ if paris_weather_data:
+ paris_weather_summary = format_weather_data(paris_weather_data)
+ except Exception as e:
+ print(f"Error fetching Paris weather: {e}")
+
+ tornado_news = get_news('tornado', 5)
+ storm_news = get_news('storm', 5)
+ flood_news = get_news('flood', 5)
+
+ return render(request, 'home.html', context={
+ 'user_location_summary': user_location_summary,
+ 'paris_weather_summary': paris_weather_summary,
+ 'tornado_news': tornado_news,
+ 'storm_news': storm_news,
+ 'flood_news': flood_news,
+ })
+
+@cache_page(60 * 15)
+def weather(request):
+ return render(request, 'weather.html')
+
+def search_suggestions(request):
+ city_name = request.GET.get('city_name', '').lower()
+
+ try:
+ json_path = 'backend/climate/fixture/city.list.json.txt'
+
+ try:
+ with open(json_path, 'r', encoding='utf-8') as file:
+ cities = json.load(file)
+ except FileNotFoundError:
+ url = "https://bulk.openweathermap.org/sample/city.list.json.gz"
+ response = requests.get(url)
+ if response.status_code == 200:
+ cities = json.loads(response.text)
+ else:
+ return JsonResponse({'success': False, 'error': f"Failed to fetch data from URL: {url}"})
+
+ suggestions = [city['name'] for city in cities if city_name in city['name'].lower()]
+ return JsonResponse({'success': True, 'suggestions': suggestions})
+
+ except json.JSONDecodeError:
+ return JsonResponse({'success': False, 'error': 'Error parsing JSON'})
+ except Exception as e:
+ return JsonResponse({'success': False, 'error': str(e)})
+
+
diff --git a/climate/Template/home.html b/climate/Template/home.html
deleted file mode 100644
index 5e6a5f2..0000000
--- a/climate/Template/home.html
+++ /dev/null
@@ -1,95 +0,0 @@
-{% load static%}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Home Page
-
-
-
-
-
-
-
- search city
-
-
-
-
-
-
-
-
Weather News
-
- {% for weather_news in weather_news_list %}
-
{{ weather_news }}
- {% endfor %}
-
-
-
most viewed location
-
-
{{ paris_weather_summary }}
-
-
-
current location
-
-
{{ user_location_summary }}
-
-
-
Tornado News
-
-
- {% for news in tornado_news %}
-
- {{ news.title }}
- {{ news.description }}
- Read more
-
- {% endfor %}
-
-
-
-
Storm News
-
-
- {% for news in storm_news %}
-
- {{ news.title }}
- {{ news.description }}
- Read more
-
- {% endfor %}
-
-
-
-
Flood News
-
-
- {% for news in flood_news %}
-
- {{ news.title }}
- {{ news.description }}
- Read more
-
- {% endfor %}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/climate/Template/weather.html b/climate/Template/weather.html
deleted file mode 100644
index 98cb736..0000000
--- a/climate/Template/weather.html
+++ /dev/null
@@ -1,80 +0,0 @@
-{% load static%}
-
-
-
-
-
-
-
-
-
-
-
-
-
- search bar
-
-
-
-
-
-
-
-
-
-
-
-
- {% if city_name %}
-
Weather in {{ city_name }}
-
Time: {{ city_time }}
-
Temperature: {{ temperature }}°C
-
Description: {{ description }}
-
WindSpeed: {{ wind_speed }}
-
Humidity: {{ humidity }}
- {% endif %}
-
-
-
-
-
-
-
-
-
-
-
- {% if rainy_weather %}
-
- {% elif sunny_weather %}
-
- {% elif snowy_weather %}
-
- {% elif tornado_weather %}
-
- {% elif cloudy_weather %}
-
- {% elif windy_weather %}
-
- {% elif moon_weather %}
-
- {% else %}
-
- {% endif %}
-
-
-
-
- Home
-
-
-
-
-
\ No newline at end of file
diff --git a/climate/__pycache__/__init__.cpython-310.pyc b/climate/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index be6ca68..0000000
Binary files a/climate/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/climate/__pycache__/settings.cpython-310.pyc b/climate/__pycache__/settings.cpython-310.pyc
deleted file mode 100644
index 84d73d6..0000000
Binary files a/climate/__pycache__/settings.cpython-310.pyc and /dev/null differ
diff --git a/climate/__pycache__/urls.cpython-310.pyc b/climate/__pycache__/urls.cpython-310.pyc
deleted file mode 100644
index e51c92e..0000000
Binary files a/climate/__pycache__/urls.cpython-310.pyc and /dev/null differ
diff --git a/climate/__pycache__/views.cpython-310.pyc b/climate/__pycache__/views.cpython-310.pyc
deleted file mode 100644
index fcbf438..0000000
Binary files a/climate/__pycache__/views.cpython-310.pyc and /dev/null differ
diff --git a/climate/__pycache__/wsgi.cpython-310.pyc b/climate/__pycache__/wsgi.cpython-310.pyc
deleted file mode 100644
index 671d002..0000000
Binary files a/climate/__pycache__/wsgi.cpython-310.pyc and /dev/null differ
diff --git a/climate/static/css/404.css b/climate/static/css/404.css
deleted file mode 100644
index 945d4aa..0000000
--- a/climate/static/css/404.css
+++ /dev/null
@@ -1,36 +0,0 @@
-body {
- background-color: black;
- background-size: 100% 100%;
- position: absolute;
- overflow: hidden;
-}
-
-.error {
- position: fixed;
- top: 40%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 48px;
- font-weight: bold;
- background: linear-gradient(
- to right,green,
- blue, purple, red,
- darkblue,darkgreen,darkred
- );
- -webkit-background-clip: text;
- color: transparent;
- background-position: left bottom;
- transition: background-position 0.5s ease-in-out;
- background-size: 300%;
- animation: shine 12s linear infinite;
- text-shadow: 0 10px 250px #00ff00;
-}
-
-@keyframes shine {
- 0% {
- background-position: left bottom;
- }
- 100% {
- background-position: right bottom;
- }
-}
diff --git a/climate/static/css/home.css b/climate/static/css/home.css
deleted file mode 100644
index 80baf82..0000000
--- a/climate/static/css/home.css
+++ /dev/null
@@ -1,230 +0,0 @@
-body {
- animation: movingColor 10s infinite;
- background: linear-gradient(180deg, rgb(176, 142, 199) 10%, #7966af 20%, rgb(16, 177, 177) 100%);
- background-size: 100% 100%;
- background-attachment: fixed;
- position: absolute;
- overflow-y: auto;
- margin: 0;
-}
-
-@keyframes movingColor {
- 0% {
- background-position: 0% 50%;
- }
-
- 50% {
- background-position: 100% 50%;
- }
-
- 100% {
- background-position: 0% 50%;
- }
-}
-
-#particles-js {
- position: fixed;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- z-index: -1;
-}
-
-.button {
- width: 500px;
- height: 50px;
- padding: 10px 20px;
- background-color: transparent;
- background: linear-gradient(
- to right,
- rgb(199, 140, 238) 33%,
- #6d51bb 66%,
- rgb(12, 190, 190) 100%
- );
- animation: gradient 5s linear infinite;
- transition: background-position 0.8s ease-in-out;
- background-position: left bottom;
- background-size: 300%;
- top: 5%;
- left: 50%;
- transform: translate(-50%, -50%);
- position: fixed;
- border: 2px solid #5e42ac;
- border-radius: 30px;
- box-shadow: 0 0 15px #4b3586, 0 0 35px #4b3586, 0 0 55px #4b3586, 0 0 100px #4b3586;
- text-align: center;
- color: black;
- font-weight: bold;
- font-size: 20px;
-}
-
-.button:hover {
- border: 2px solid rgb(16, 177, 177);
- box-shadow: 0 0 15px rgb(4, 214, 214), 0 0 35px rgb(4, 214, 214), 0 0 55px rgb(4, 214, 214), 0 0 100px rgb(4, 214, 214);
- transition: background-position 0.8s ease-in-out;
-}
-
-@keyframes gradient {
- 0% {
- background-position: left bottom;
- }
-
- 100% {
- background-position: right bottom;
- }
-}
-
-#dark-mode-toggle {
- position: fixed;
- z-index: 999;
-}
-
-#dark-mode-toggle {
- top: 2.5%;
- right: 50px;
- cursor: pointer;
-}
-
-#dark-mode-icon {
- font-size: 42px;
- color: cyan;
-}
-
-body.dark-mode {
- animation: movingColor 10s infinite;
- background: linear-gradient(180deg, rgb(54, 28, 71) 10%, #20173a 20%, rgb(5, 78, 78) 100%);
- background-size: 100% 100%;
- background-attachment: fixed;
- position: absolute;
- overflow: hidden;
- margin: 0;
- color: rgb(1, 85, 85);
-}
-
-.dark-mode {
- background-color: #34495e;
- border: 5px double #0be9f0;
-}
-
-.dark-mode .home-container,
-.dark-mode .section img,
-.dark-mode .section {
- background-color: #34495e7e;
- border: 2px double rgb(6, 80, 80);
-}
-
-#github-link {
- top: 2.5%;
- left: 50px;
- cursor: pointer;
- position: fixed;
- color: cyan;
- font-size: 42px;
-}
-
-#github-link:hover,
-#dark-mode-icon:hover {
- text-shadow: 0 0 15px rgb(4, 214, 214), 0 0 35px rgb(4, 214, 214), 0 0 55px rgb(4, 214, 214), 0 0 100px rgb(4, 214, 214);
-}
-
-.home-container {
- left: 50%;
- top: 100px;
- transform: translate(-50%);
- position: fixed;
- margin: 0px auto;
- width: 1000px;
- height: 900px;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- background-color: #376ea57e;
- border-radius: 15px;
- border: 2px double cyan;
-}
-
-.home-container li,
-.home-container p {
- color: white;
- font-size: 18px;
- font-weight: lighter;
- text-align: center;
- margin: 0;
-}
-
-.home-container h1{
- left: 50%;
- transform: translate(-50%);
- position: fixed;
- text-align: center;
- font-size: 24px;
- font-weight: bold;
- color: black;
-}
-
-.logo {
- cursor: pointer;
- color: black;
- font-size: 14px;
- margin-top: 50px;
- left: 50%;
- transform: translate(-25%);
- position: relative;
- z-index: 999;
-}
-
-.section {
- width: 900px;
- height: auto;
- border: 2px double cyan;
- margin: 50px auto;
- background-color: #34495e7e;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
-.section h {
- text-align: center;
- font-size: 24px;
- font-weight: bold;
- color: black;
- margin-top: 20px;
-}
-
-.section img {
- left: 50%;
- transform: translate(-50%);
- position: relative;
- width: 600px;
- height: 400px;
- border: 2px double cyan;
- margin-top: 70px;
-}
-
-.section p,
-.section li {
- color: white;
- font-size: 18px;
- font-weight: lighter;
- text-align: center;
- margin: 0;
- margin-top: 70px;
- margin-bottom: 50px;
-}
-
-.section-link {
- position: fixed;
- color: black;
- font-size: 14px;
- font-weight: bold;
- left: 90%;
- transform: translate(-50%);
- cursor: pointer;
- margin-top: 0px;
-}
-
-.section-link:hover {
- color: blue;
- text-decoration: underline;
-}
\ No newline at end of file
diff --git a/climate/static/css/weather.css b/climate/static/css/weather.css
deleted file mode 100644
index fc41ffc..0000000
--- a/climate/static/css/weather.css
+++ /dev/null
@@ -1,342 +0,0 @@
-body {
- animation: movingColor 10s infinite;
- background: linear-gradient(180deg,rgb(176, 142, 199) 10%, #7966af 20% ,rgb(16, 177, 177) 100%) ;
- background-size: 100% 100%;
- background-attachment: fixed;
- position: absolute;
- overflow: auto;
- margin: 0;
-}
-
-@keyframes movingColor {
- 0% {
- background-position: 0% 50%;
- }
-
- 50% {
- background-position: 100% 50%;
- }
-
- 100% {
- background-position: 0% 50%;
- }
-}
-
-#particles-js {
- position: fixed;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- z-index: -1;
-}
-
-.search-container{
- left: 50%;
- top: 50%;
- transform: translate(-50%,-50%);
- position: fixed;
- width: 800px;
- height: 600px;
- background-color: #376ea57e;
- border-radius: 15px;
- border: 2px double cyan;
-}
-
-.search-container:hover{
- box-shadow: 0 0 15px rgb(4, 214, 214), 0 0 35px rgb(4, 214, 214), 0 0 55px rgb(4, 214, 214), 0 0 100px rgb(4, 214, 214);
-}
-
-.button{
- cursor: pointer;
- width: 500px;
- height: 50px;
- padding: 10px 20px;
- background-color: transparent;
- background: linear-gradient(
- to right,
- rgb(199, 140, 238) 33%,
- #6d51bb 66%,
- rgb(12, 190, 190) 100%
- );
- animation: gradient 5s linear infinite;
- transition: background-position 0.8s ease-in-out;
- background-position: left bottom;
- background-size: 300%;
- bottom: 5%;
- left: 50%;
- transform: translate(-50%, -50%);
- position: fixed;
- border: 2px solid #5e42ac;
- border-radius: 30px;
- box-shadow: 0 0 15px #4b3586, 0 0 35px #4b3586, 0 0 55px #4b3586, 0 0 100px #4b3586;
- text-align: center;
- color: black;
- font-weight: bold;
- font-size: 20px;
-}
-
-.button:hover{
- border: 2px solid rgb(16, 177, 177);
- box-shadow: 0 0 15px rgb(4, 214, 214), 0 0 35px rgb(4, 214, 214), 0 0 55px rgb(4, 214, 214), 0 0 100px rgb(4, 214, 214);
- transition: background-position 0.8s ease-in-out;
-}
-
-@keyframes gradient {
- 0% {
- background-position: left bottom;
- }
-
- 100% {
- background-position: right bottom;
- }
-}
-
-#dark-mode-toggle{
- position: fixed;
- z-index: 999;
-}
-
-#dark-mode-toggle {
- top: 2.5%;
- right: 50px;
- cursor: pointer;
-}
-
-#dark-mode-icon {
- font-size: 42px;
- color: cyan;
-}
-
-body.dark-mode {
- animation: movingColor 10s infinite;
- background: linear-gradient(180deg, rgb(54, 28, 71) 10%, #20173a 20%, rgb(5, 78, 78) 100%);
- background-size: 100% 100%;
- background-attachment: fixed;
- position: absolute;
- overflow: hidden;
- margin: 0;
- color: rgb(1, 85, 85);
-}
-
-.dark-mode {
- background-color: #34495e;
- border: 5px double #0be9f0;
-}
-
-.dark-mode .search-container:hover{
- box-shadow: 0 0 15px rgb(12, 77, 77), 0 0 55px rgb(12, 77, 77), 0 0 75px rgb(12, 77, 77),0 0 120px rgb(12, 77, 77);
-}
-
-.dark-mode .search-container{
- background-color: #34495e7e;
- border: 2px double rgb(6, 80, 80);
-}
-
-.dark-mode .u3-icon{
- color: #04484b;
-}
-
-#github-link{
- top: 2.5%;
- left: 50px;
- cursor: pointer;
- position: fixed;
- color: cyan;
- font-size: 42px;
-}
-
-#github-link:hover,
-#dark-mode-icon:hover{
- text-shadow:0 0 15px rgb(4, 214, 214),0 0 35px rgb(4, 214, 214),0 0 55px rgb(4, 214, 214),0 0 100px rgb(4, 214, 214);
-}
-
-.input-container {
- left: 50%;
- top: 15%;
- position: fixed;
- transform: translate(-50%,-50%);
-}
-
-.input-container i {
- position: absolute;
- top: 75%;
- transform: translateY(-50%);
- left: -40px;
- color: black;
- font-size: 24px;
-}
-
-.input-container label {
- left: 70px;
- top: -20px;
- position: relative;
- font-weight: bolder;
- font-size: 32px;
- background: linear-gradient(
- to right,
- rgb(100, 44, 138),
- #392966,
- rgb(4, 85, 85),
- rgb(54, 28, 71),
- #20173a,
- rgb(5, 78, 78)
- );
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- background-position: left bottom;
- transition: background-position 0.5s ease-in-out;
- background-size: 300%;
- animation: shine 12s linear infinite;
-}
-
-@keyframes shine {
- 0% {
- background-position: left bottom;
- }
- 100% {
- background-position: right bottom;
- }
-}
-
-input {
- width: 450px;
- padding: 10px;
- border: 2px solid cyan;
- background-color: rgba(245, 222, 179, 0.568);
- border-radius: 15px;
- color: black;
- outline: none;
-}
-
-input::placeholder {
- color: black;
-}
-
-input:hover{
- border: 2px solid rgb(13, 128, 128);
-}
-
-
-
-.weather-container h2{
- left: 50%;
- top: 35%;
- transform: translate(-50%,-50%);
- position: fixed;
- color: black;
- font-weight: bolder;
- font-size: 24px;
- text-align: center;
-}
-
-.weather-container h3{
- left: 20%;
- top: 50%;
- transform: translate(-50%,-50%);
- position: fixed;
- color: black;
- font-weight: lighter;
- font-size: 14px;
- text-align: left;
-}
-.h3-icon{
- top: 52%;
- left: 11%;
- transform: translate(-50%,-50%);
- position: fixed;
- font-size: 32px;
-}
-
-.weather-container p{
- top: 50%;
- left: 83%;
- transform: translate(-50%,-50%);
- position: fixed;
- color: black;
- font-weight: lighter;
- font-size: 14px;
- text-align: left;
-}
-.weather-container i{
- top: 52%;
- left: 72%;
- transform: translate(-50%,-50%);
- position: fixed;
- font-size: 32px;
-}
-
-.u2{
- top: 60%;
- left: 21%;
- transform: translate(-50%,-50%);
- position:fixed;
- color: black;
- font-size: 14px;
- font-weight: lighter;
-}
-.u2-icon{
- top: 60%;
- left: 11%;
- transform: translate(-50%,-50%);
- position: fixed;
- font-size: 32px;
-}
-
-.u3{
- top: 61%;
- left: 80%;
- transform: translate(-50%,-50%);
- position: fixed;
- color: black;
- font-weight: lighter;
- font-size: 14px;
- text-align: left;
-}
-.u3-icon{
- top: 61%;
- left: 71.5%;
- transform: translate(-50%,-50%);
- position: fixed;
- font-size: 32px;
- color: blue;
-}
-
-.u1{
- top: 75%;
- left: 50%;
- transform: translate(-50%,-50%);
- position: fixed;
- text-align: left;
- color: black;
- font-weight: lighter;
- font-size: 14px;
-}
-.weather{
- top: 70%;
- left: 50%;
- transform: translate(-50%,-50%);
- position: fixed;
- font-size: 32px;
-}
-
-.suggestions-container {
- display: none;
- position: absolute;
- width: 100%;
- max-height: 150px;
- overflow-y: auto;
- background-color: white;
- border: 1px solid #ccc;
- z-index: 100;
-}
-
-.suggestion-item {
- padding: 10px;
- cursor: pointer;
-}
-
-.suggestion-item:hover {
- background-color: #f5f5f5;
-}
\ No newline at end of file
diff --git a/climate/static/js/home.js b/climate/static/js/home.js
deleted file mode 100644
index 20cb282..0000000
--- a/climate/static/js/home.js
+++ /dev/null
@@ -1,132 +0,0 @@
-particlesJS("particles-js", {
- particles: {
- number: {
- value: 150,
- density: {
- enable: true,
- value_area: 800
- }
- },
- color: {
- value: "#ffffff"
- },
- shape: {
- type: "circle",
- stroke: {
- width: 0,
- color: "#000000"
- },
- polygon: {
- nb_sides: 5
- },
- image: {
- src: "path/to/your/snowdrop-image.png",
- width: 100,
- height: 100
- }
- },
- opacity: {
- value: 0.5,
- random: true,
- anim: {
- enable: true,
- speed: 1,
- opacity_min: 0,
- sync: false
- }
- },
- size: {
- value: 10,
- random: true,
- anim: {
- enable: true,
- speed: 3,
- size_min: 0,
- sync: false
- }
- },
- line_linked: {
- enable: false
- },
- move: {
- enable: true,
- speed: 1,
- direction: "bottom",
- random: false,
- straight: false,
- out_mode: "out",
- bounce: false,
- attract: {
- enable: false,
- rotateX: 600,
- rotateY: 1200
- }
- }
- },
- interactivity: {
- detect_on: "canvas",
- events: {
- onhover: {
- enable: false
- },
- onclick: {
- enable: false
- },
- resize: true
- }
- },
- retina_detect: true
- });
-
-function toggleDarkMode() {
- const body = document.body;
- const darkModeIcon = document.getElementById('dark-mode-icon');
-
- body.classList.toggle('dark-mode');
-
- darkModeIcon.classList.toggle('fa-moon');
- darkModeIcon.classList.toggle('fa-sun');
-
- if (body.classList.contains('dark-mode')) {
- body.style.setProperty('background', 'linear-gradient(180deg, rgb(54, 28, 71) 10%, #20173a 20%, rgb(5, 78, 78) 100%)', 'important');
- body.style.setProperty('background-attachment', 'fixed', 'important');
- body.style.setProperty('animation', 'movingColor 10s infinite', 'important');
- body.style.setProperty('overflow','hidden','important');
- } else {
- body.style.setProperty('background', 'linear-gradient(180deg, rgb(176, 142, 199) 10%, #7966af 20%, rgb(16, 177, 177) 100%)', 'important');
- body.style.removeProperty('background-attachment');
- body.style.removeProperty('animation');
- body.style.setProperty('overflow');
- }
-}
-
-document.addEventListener('DOMContentLoaded', function () {
- getLocation();
- const homeButton = document.getElementById('searchButton');
- homeButton.addEventListener('click', function () {
- window.location.href = "../weather/";
- });
-});
-
-
-function getLocation() {
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition(showPosition, showError);
- } else {
- console.error("Geolocation is not supported by this browser.");
- }
-}
-
-function showPosition(position) {
- fetch(`/get_user_location/${position.coords.latitude}/${position.coords.longitude}/`)
- .then(response => response.json())
- .then(data => {
- console.log(data);
- })
- .catch(error => console.error('Error:', error));
-}
-
-function showError(error) {
- console.error('Geolocation error:', error);
- alert("Error getting your location. Please check your browser settings or try again later.");
-}
diff --git a/climate/static/js/weather.js b/climate/static/js/weather.js
deleted file mode 100644
index 75b7212..0000000
--- a/climate/static/js/weather.js
+++ /dev/null
@@ -1,155 +0,0 @@
-particlesJS("particles-js", {
- particles: {
- number: {
- value: 150,
- density: {
- enable: true,
- value_area: 800
- }
- },
- color: {
- value: "#ffffff"
- },
- shape: {
- type: "circle",
- stroke: {
- width: 0,
- color: "#000000"
- },
- polygon: {
- nb_sides: 5
- },
- image: {
- src: "path/to/your/snowdrop-image.png",
- width: 100,
- height: 100
- }
- },
- opacity: {
- value: 0.5,
- random: true,
- anim: {
- enable: true,
- speed: 1,
- opacity_min: 0,
- sync: false
- }
- },
- size: {
- value: 10,
- random: true,
- anim: {
- enable: true,
- speed: 3,
- size_min: 0,
- sync: false
- }
- },
- line_linked: {
- enable: false
- },
- move: {
- enable: true,
- speed: 1,
- direction: "bottom",
- random: false,
- straight: false,
- out_mode: "out",
- bounce: false,
- attract: {
- enable: false,
- rotateX: 600,
- rotateY: 1200
- }
- }
- },
- interactivity: {
- detect_on: "canvas",
- events: {
- onhover: {
- enable: false
- },
- onclick: {
- enable: false
- },
- resize: true
- }
- },
- retina_detect: true
- });
-
-function toggleDarkMode() {
- const body = document.body;
- const darkModeIcon = document.getElementById('dark-mode-icon');
-
- body.classList.toggle('dark-mode');
-
- darkModeIcon.classList.toggle('fa-moon');
- darkModeIcon.classList.toggle('fa-sun');
-
- if (body.classList.contains('dark-mode')) {
- body.style.setProperty('background', 'linear-gradient(180deg, rgb(54, 28, 71) 10%, #20173a 20%, rgb(5, 78, 78) 100%)', 'important');
- body.style.setProperty('background-attachment', 'fixed', 'important');
- body.style.setProperty('animation', 'movingColor 10s infinite', 'important');
- body.style.setProperty('overflow','hidden','important');
- } else {
- body.style.setProperty('background', 'linear-gradient(180deg, rgb(176, 142, 199) 10%, #7966af 20%, rgb(16, 177, 177) 100%)', 'important');
- body.style.removeProperty('background-attachment');
- body.style.removeProperty('animation');
- body.style.setProperty('overflow');
- }
-}
-
-document.addEventListener('DOMContentLoaded', function () {
- const homeButton = document.getElementById('homeButton');
- homeButton.addEventListener('click', function () {
- window.location.href = '../home/';
- });
-});
-
-document.addEventListener('DOMContentLoaded', function () {
- const searchInput = document.getElementById('search');
- const suggestionsContainer = document.getElementById('suggestions-container');
-
- searchInput.addEventListener('input', function () {
- handleInput(searchInput.value);
- });
-
- searchInput.addEventListener('keydown', function (event) {
- handleKeyDown(event);
- });
-
- function handleInput(input) {
- suggestionsContainer.innerHTML = "";
-
- if (input.length >= 3) {
- fetch(`/search_suggestions/?city_name=${input}`)
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- data.suggestions.slice(0, 5).forEach(suggestion => {
- const suggestionItem = document.createElement("div");
- suggestionItem.textContent = suggestion;
- suggestionItem.addEventListener('click', function () {
- searchInput.value = suggestion;
- suggestionsContainer.innerHTML = "";
- });
- suggestionsContainer.appendChild(suggestionItem);
- });
- } else {
- console.error("Error fetching suggestions:", data.error);
- }
- })
- .catch(error => {
- console.error("Error fetching suggestions:", error);
- });
- }
- }
-
- function handleKeyDown(event) {
- if (event.key === "Enter") {
- const cityName = searchInput.value;
- console.log("Search for weather in " + cityName);
- }
- }
-});
\ No newline at end of file
diff --git a/db.sqlite3 b/db.sqlite3
deleted file mode 100644
index da3d2c5..0000000
Binary files a/db.sqlite3 and /dev/null differ
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..6cb208d
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2456 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "tailwind-scrollbar": "^3.1.0",
+ "tailwindcss-textshadow": "^2.1.3"
+ },
+ "devDependencies": {
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.15"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz",
+ "integrity": "sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw==",
+ "license": "MIT",
+ "dependencies": {
+ "postcss": "7.0.32",
+ "purgecss": "^2.3.0"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/chalk/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/postcss": {
+ "version": "7.0.32",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.32.tgz",
+ "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ },
+ "node_modules/@fullhuman/postcss-purgecss/node_modules/supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "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.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "license": "MIT",
+ "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.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "license": "MIT",
+ "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.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "license": "MIT",
+ "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.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmmirror.com/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.20",
+ "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.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.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.2",
+ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.2.tgz",
+ "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001669",
+ "electron-to-chromium": "^1.5.41",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "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.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001684",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz",
+ "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==",
+ "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"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "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.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",
+ "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.3",
+ "color-string": "^1.6.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/color/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-unit-converter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
+ "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==",
+ "license": "MIT"
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/defined": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/defined/-/defined-1.0.1.tgz",
+ "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/detective": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/detective/-/detective-5.2.1.tgz",
+ "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn-node": "^1.8.2",
+ "defined": "^1.0.0",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "detective": "bin/detective.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "license": "MIT"
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.67",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz",
+ "integrity": "sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==",
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "license": "MIT",
+ "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.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.0.tgz",
+ "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "license": "ISC",
+ "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.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-tags": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz",
+ "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+ "license": "MIT",
+ "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.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "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.6",
+ "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-emoji": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmmirror.com/node-emoji/-/node-emoji-1.11.0.tgz",
+ "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.18.tgz",
+ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize.css": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmmirror.com/normalize.css/-/normalize.css-8.0.1.tgz",
+ "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==",
+ "license": "MIT"
+ },
+ "node_modules/num2fraction": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/num2fraction/-/num2fraction-1.2.2.tgz",
+ "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==",
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "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/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-functions": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/postcss-functions/-/postcss-functions-3.0.0.tgz",
+ "integrity": "sha512-N5yWXWKA+uhpLQ9ZhBRl2bIAdM6oVJYpDojuI1nF2SzXBimJcdjFwiAouBVbO5VuOF3qA6BSFWFc3wXbbj72XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^7.1.2",
+ "object-assign": "^4.1.1",
+ "postcss": "^6.0.9",
+ "postcss-value-parser": "^3.3.0"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/postcss-functions/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/postcss": {
+ "version": "6.0.23",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-6.0.23.tgz",
+ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.4.1",
+ "source-map": "^0.6.1",
+ "supports-color": "^5.4.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/postcss-functions/node_modules/postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "license": "MIT"
+ },
+ "node_modules/postcss-functions/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "license": "MIT",
+ "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.0.1",
+ "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-load-config/node_modules/lilconfig": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.2.tgz",
+ "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "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.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "license": "MIT",
+ "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.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/pretty-hrtime": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+ "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/purgecss": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/purgecss/-/purgecss-2.3.0.tgz",
+ "integrity": "sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^5.0.0",
+ "glob": "^7.0.0",
+ "postcss": "7.0.32",
+ "postcss-selector-parser": "^6.0.2"
+ },
+ "bin": {
+ "purgecss": "bin/purgecss"
+ }
+ },
+ "node_modules/purgecss/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/purgecss/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/purgecss/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/purgecss/node_modules/chalk/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/purgecss/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/purgecss/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/purgecss/node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/purgecss/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/purgecss/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/purgecss/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/purgecss/node_modules/postcss": {
+ "version": "7.0.32",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.32.tgz",
+ "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ },
+ "node_modules/purgecss/node_modules/supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/reduce-css-calc": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmmirror.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
+ "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
+ "license": "MIT",
+ "dependencies": {
+ "css-unit-converter": "^1.1.1",
+ "postcss-value-parser": "^3.3.0"
+ }
+ },
+ "node_modules/reduce-css-calc/node_modules/postcss-value-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "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.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "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.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "license": "MIT",
+ "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.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "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.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "license": "MIT",
+ "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-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwind-scrollbar": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz",
+ "integrity": "sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "tailwindcss": "3.x"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.15",
+ "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.15.tgz",
+ "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==",
+ "license": "MIT",
+ "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.6",
+ "lilconfig": "^2.1.0",
+ "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",
+ "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/tailwindcss-textshadow": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/tailwindcss-textshadow/-/tailwindcss-textshadow-2.1.3.tgz",
+ "integrity": "sha512-FGVHfK+xnV879VSQDeRvY61Aa+b0GDiGaFBPwCOKvqIrK57GyepWJL1GydjtGOLHE9qqphFucRNj9fHramCzNg==",
+ "license": "MIT",
+ "dependencies": {
+ "tailwindcss": "^1.2.0"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/autoprefixer": {
+ "version": "9.8.8",
+ "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-9.8.8.tgz",
+ "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==",
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.12.0",
+ "caniuse-lite": "^1.0.30001109",
+ "normalize-range": "^0.1.2",
+ "num2fraction": "^1.2.2",
+ "picocolors": "^0.2.1",
+ "postcss": "^7.0.32",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/object-hash": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-2.2.0.tgz",
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/picocolors": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz",
+ "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
+ "license": "ISC"
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/postcss": {
+ "version": "7.0.39",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz",
+ "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^0.2.1",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/postcss-js": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-2.0.3.tgz",
+ "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==",
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1",
+ "postcss": "^7.0.18"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/postcss-nested": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-4.2.3.tgz",
+ "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==",
+ "license": "MIT",
+ "dependencies": {
+ "postcss": "^7.0.32",
+ "postcss-selector-parser": "^6.0.2"
+ }
+ },
+ "node_modules/tailwindcss-textshadow/node_modules/tailwindcss": {
+ "version": "1.9.6",
+ "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-1.9.6.tgz",
+ "integrity": "sha512-nY8WYM/RLPqGsPEGEV2z63riyQPcHYZUJpAwdyBzVpxQHOHqHE+F/fvbCeXhdF1+TA5l72vSkZrtYCB9hRcwkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@fullhuman/postcss-purgecss": "^2.1.2",
+ "autoprefixer": "^9.4.5",
+ "browserslist": "^4.12.0",
+ "bytes": "^3.0.0",
+ "chalk": "^3.0.0 || ^4.0.0",
+ "color": "^3.1.2",
+ "detective": "^5.2.0",
+ "fs-extra": "^8.0.0",
+ "html-tags": "^3.1.0",
+ "lodash": "^4.17.20",
+ "node-emoji": "^1.8.1",
+ "normalize.css": "^8.0.1",
+ "object-hash": "^2.0.3",
+ "postcss": "^7.0.11",
+ "postcss-functions": "^3.0.0",
+ "postcss-js": "^2.0.0",
+ "postcss-nested": "^4.1.1",
+ "postcss-selector-parser": "^6.0.0",
+ "postcss-value-parser": "^4.1.0",
+ "pretty-hrtime": "^1.0.3",
+ "reduce-css-calc": "^2.1.6",
+ "resolve": "^1.14.2"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+ "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.0"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "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.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "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.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "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.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "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.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "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.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.6.1.tgz",
+ "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..c7c71bf
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "devDependencies": {
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.15"
+ },
+ "dependencies": {
+ "tailwind-scrollbar": "^3.1.0",
+ "tailwindcss-textshadow": "^2.1.3"
+ }
+}
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
new file mode 100644
index 0000000..33ad091
--- /dev/null
+++ b/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/frontend/requirements.txt b/frontend/requirements.txt
new file mode 100644
index 0000000..bdcf261
--- /dev/null
+++ b/frontend/requirements.txt
@@ -0,0 +1,6 @@
+# Tailwind CSS and related tools
+tailwindcss
+autoprefixer
+postcss
+
+
diff --git a/frontend/src/input.css b/frontend/src/input.css
new file mode 100644
index 0000000..29b426a
--- /dev/null
+++ b/frontend/src/input.css
@@ -0,0 +1,202 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body::-webkit-scrollbar {
+ width: 12px;
+}
+
+body::-webkit-scrollbar-track {
+ background: var(--tw-scrollbar-track);
+ border-radius: 10px;
+}
+
+body::-webkit-scrollbar-thumb {
+ background-color: var(--tw-scrollbar-thumb);
+ border-radius: 10px;
+ border: 3px solid var(--tw-scrollbar-track);
+}
+
+body::-webkit-scrollbar-thumb:hover {
+ background-color: #1ad50d;
+}
+
+body.dark::-webkit-scrollbar-thumb {
+ background-color: #0be9f0;
+}
+
+body.dark::-webkit-scrollbar-thumb:hover {
+ background-color: #1ad50d;
+}
+
+@layer base {
+ body {
+ @apply m-0 p-5 sm:p-4 md:p-6 font-sans text-white bg-gradient-to-br from-[#e1d0b1] via-[#a3b8c9] to-[#9cd1d1] bg-fixed animate-movingColor scrollbar scrollbar-thin scrollbar-thumb-[#5a5a5a] scrollbar-track-[#e3e3e3] dark:bg-gradient-to-br dark:from-[#292738] dark:via-[#3c3750] dark:to-[#565d7b] dark:text-white dark:scrollbar-track-[#3c3750] dark:scrollbar-thumb-[#0be9f0];
+ }
+
+ #particles-js {
+ @apply w-full h-full fixed top-0 left-0 z-[-1];
+ }
+}
+
+@layer components {
+ .home-main {
+ @apply relative mx-auto w-[calc(100%_-_2rem)] max-w-[1000px] min-h-screen pb-[100px] bg-[#d7dbdd70] flex flex-col overflow-y-auto bg-opacity-80 border-[4px] border-double border-[#7fb3d5] rounded-lg dark:bg-opacity-80 dark:bg-[#34495e] dark:border-[#065050];
+ }
+
+ .search-container {
+ @apply relative mx-auto w-[calc(100%_-_2rem)] max-w-[1000px] h-[80vh] pb-[100px] bg-[#d7dbdd70] flex flex-col overflow-hidden bg-opacity-80 border-[4px] border-double border-[#7fb3d5] rounded-lg dark:bg-opacity-80 dark:bg-[#34495e] dark:border-[#065050];
+ }
+
+ section {
+ @apply flex flex-col justify-between mt-[50px] mx-auto w-[calc(100%_-_2rem)] max-w-[900px] bg-[#d7dbdd70] h-auto bg-opacity-80 border-2 border-solid border-[#7fb3d5] rounded-sm dark:bg-opacity-80 dark:bg-[#34495e] dark:border-[#065050];
+ }
+
+ section img {
+ @apply relative left-1/2 -translate-x-1/2 w-[600px] h-[400px] border-[1px] border-solid border-white mt-[50px] mb-[25px] rounded-lg shadow-lg dark:border-black dark:shadow-xl dark:brightness-90;
+ }
+
+ .title {
+ @apply absolute left-1/2 -translate-x-1/2 text-center text-[28px] sm:text-[32px] md:text-[36px] font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-[#9cd1d1] to-[#7fb3d5] dark:from-[#0B5563] dark:to-[#065050] drop-shadow-lg transition-all;
+ }
+
+ .news-description,
+ .search-description {
+ @apply text-left text-[16px] sm:text-[18px] md:text-[20px] font-medium text-gray-800 dark:text-gray-300 leading-relaxed mx-auto mt-[75px] px-4 sm:px-6 transition-all ease-in-out duration-300;
+ }
+
+ .button-search,
+ .button-home {
+ @apply relative mx-auto px-6 py-3 mt-5 text-[16px] md:text-[18px] font-semibold text-white transition-all duration-300 ease-in-out transform bg-gradient-to-tr from-[#9cd1d1] to-[#7fb3d5] shadow-lg rounded-lg cursor-pointer hover:from-[#00ffff] hover:to-[#008080] hover:shadow-xl active:scale-95 active:bg-[#00ced1] dark:bg-gradient-to-tr dark:from-[#0B5563] dark:to-[#065050] dark:hover:from-[#008080] dark:hover:to-[#0be9f0] dark:active:bg-[#007070];
+ }
+
+ .section-link {
+ @apply text-[#007BFF] font-bold underline transition-colors duration-300 hover:text-[#0056b3] dark:text-[#00E0FF] dark:hover:text-[#007BFF];
+ }
+
+ .news-list {
+ @apply flex flex-col gap-6 mt-16 px-6 md:px-8 lg:px-10 w-full max-w-[900px] mx-auto;
+ }
+
+ .news-item {
+ @apply flex flex-col bg-white dark:bg-[#2c3e50] p-4 rounded-lg shadow-lg transition-all pt-[5%] hover:shadow-xl border border-[#e0e0e0] dark:border-[#34495e];
+ }
+
+ .news-title {
+ @apply text-xl font-bold text-[#333] dark:text-white mb-2;
+ }
+
+ .news-description {
+ @apply text-base text-[#555] dark:text-[#ccc] mb-4 leading-relaxed;
+ }
+
+ .suggestions-container {
+ @apply max-h-[300px] overflow-y-auto border border-[#ccc] rounded-lg shadow-md bg-white z-10 absolute top-full left-0;
+ }
+
+ .suggestions-container div {
+ @apply p-2 cursor-pointer transition-colors duration-300 ease-in-out;
+ }
+
+ .suggestions-container div:hover {
+ @apply bg-[#f1f1f1] text-[#2c3e50];
+ }
+
+ .suggestions-container div:active {
+ @apply bg-[#e2e2e2];
+ }
+
+ .suggestions-container div:focus {
+ @apply outline-none bg-[#e1f5fe];
+ }
+
+ .weather-details {
+ @apply flex flex-col justify-center items-center gap-5 mt-[10vh] w-[80%] md:w-[70%] mx-auto;
+ }
+
+ .city-info {
+ @apply text-center p-6 bg-gradient-to-r from-[#6a7f8d] to-[#4a5f68] rounded-lg shadow-xl mb-6;
+ }
+
+ .subtitle {
+ @apply text-xl font-semibold text-[#2c3e50] dark:text-white mt-2;
+ }
+
+ .weather-summary {
+ @apply text-lg text-[#d1d1d1] mt-4;
+ }
+
+ .hourly-weather {
+ @apply flex gap-6 p-4 overflow-x-auto scrollbar-thin scrollbar-thumb-[#7fb3d5] dark:scrollbar-thumb-[#065050] rounded-lg text-[#2c3e50] dark:text-white bg-white dark:bg-[#2e3a45] shadow-lg;
+ }
+
+ .hour-card {
+ @apply min-w-[120px] flex flex-col items-center text-[#34495e] dark:text-white bg-[#d7dbdd] dark:bg-[#34495e] p-4 rounded-xl shadow-lg transform transition-all hover:scale-105 hover:shadow-xl hover:bg-[#e1e9f0] dark:hover:bg-[#2c3e50];
+ }
+
+ .weather-icon {
+ @apply w-[50px] h-[50px] object-contain mb-2;
+ }
+
+ .hour-time {
+ @apply text-xl font-medium text-[#34495e] dark:text-white;
+ }
+
+ .hour-temp {
+ @apply text-xl font-semibold text-[#2c3e50] dark:text-white mt-2;
+ }
+}
+
+@layer utilities {
+ .hover\:scale-110 {
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+ }
+ .hover\:rotate-180 {
+ transition: transform 0.3s ease;
+ }
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
+.error {
+ background: linear-gradient(
+ to right,green,
+ blue, purple, red,
+ darkblue,darkgreen,darkred
+ );
+ -webkit-background-clip: text;
+ color: transparent;
+ background-position: left bottom;
+ transition: background-position 0.5s ease-in-out;
+ background-size: 300%;
+ animation: shine 12s linear infinite;
+}
+
+.result-container,
+.input-container {
+ transition: all 0.3s ease-in-out;
+}
+.opacity-0 {
+ opacity: 0;
+}
+.opacity-100 {
+ opacity: 1;
+}
+.translate-y-4 {
+ transform: translateY(1rem);
+}
+.translate-y-0 {
+ transform: translateY(0);
+}
\ No newline at end of file
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000..c42e27d
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,97 @@
+module.exports = {
+ content: [
+ "./src/**/*.{js,jsx,ts,tsx}",
+ "../backend/climate/Template/*.html"
+ ],
+ darkMode: 'class',
+ theme: {
+ extend: {
+ keyframes: {
+ 'bounce-up': {
+ '0%, 20%, 50%, 80%, 100%': { transform: 'translateY(0)' },
+ '40%': { transform: 'translateY(-10px)' },
+ '60%': { transform: 'translateY(-6px)' },
+ },
+ movingColor: {
+ '0%': { backgroundPosition: '0% 50%' },
+ '50%': { backgroundPosition: '100% 50%' },
+ '100%': { backgroundPosition: '0% 50%' },
+ },
+ shine: {
+ '0%': { backgroundPosition: 'left bottom' },
+ '100%': { backgroundPosition: 'right bottom' },
+ },
+ leSnake: {
+ '0%, 100%': { transform: 'translateY(0px)' },
+ '50%': { transform: 'translateY(-5px)' },
+ },
+ lightningText: {
+ '0%, 50%, 100%': { color: 'white' },
+ '25%, 75%': { color: '#7fb3d5' },
+ },
+ },
+ animation: {
+ movingColor: 'movingColor 10s infinite',
+ 'bounce-up': 'bounce-up 1.5s infinite',
+ shine: 'shine 12s infinite',
+ leSnake: 'leSnake 1.5s ease-in-out infinite',
+ lightningText: 'lightningText 3s infinite',
+ },
+ animationDelay: {
+ '0': '0s',
+ '100': '0.1s',
+ '200': '0.2s',
+ '300': '0.3s',
+ '400': '0.4s',
+ '500': '0.5s',
+ '600': '0.6s',
+ '700': '0.7s',
+ '800': '0.8s',
+ '900': '0.9s',
+ },
+ backgroundSize: {
+ 'full': '100% 100%',
+ },
+ boxShadow: {
+ },
+ colors: {
+ 'custom-gradient-start': '#00ff00',
+ 'custom-gradient-1': '#0000ff',
+ 'custom-gradient-2': '#800080',
+ 'custom-gradient-3': '#ff0000',
+ 'custom-gradient-4': '#00008b',
+ 'custom-gradient-5': '#006400',
+ 'custom-gradient-6': '#8b0000',
+ },
+ backgroundImage: {
+ 'custom-gradient': 'linear-gradient(to right, var(--tw-gradient-stops))',
+ },
+ textShadow: {
+ 'error-shadow': '0 10px 250px #00ff00',
+ },
+ },
+ },
+ variants: {
+ extend: {
+ animation: ['hover', 'focus'],
+ },
+ },
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.delay-0': { 'animation-delay': '0s' },
+ '.delay-100': { 'animation-delay': '0.1s' },
+ '.delay-200': { 'animation-delay': '0.2s' },
+ '.delay-300': { 'animation-delay': '0.3s' },
+ '.delay-400': { 'animation-delay': '0.4s' },
+ '.delay-500': { 'animation-delay': '0.5s' },
+ '.delay-600': { 'animation-delay': '0.6s' },
+ '.delay-700': { 'animation-delay': '0.7s' },
+ '.delay-800': { 'animation-delay': '0.8s' },
+ '.delay-900': { 'animation-delay': '0.9s' },
+ });
+ },
+ require('tailwind-scrollbar')({ nocompatible: true }),
+ require('tailwindcss-textshadow'),
+ ],
+}
\ No newline at end of file
diff --git a/log.txt b/log.txt
deleted file mode 100644
index 015fe94..0000000
--- a/log.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-[2024-02-28 16:11:12] [INFO] [django.server:212] "GET /weather/?city_name=tehran HTTP/1.1" 200 2825
-[2024-02-28 16:11:17] [INFO] [django.server:212] "GET /search_suggestions/?city_name=new HTTP/1.1" 200 8456
-[2024-02-28 16:11:25] [INFO] [django.server:212] "GET /search_suggestions/?city_name=mos HTTP/1.1" 200 10493
-[2024-02-28 16:11:25] [INFO] [django.server:212] "GET /search_suggestions/?city_name=mosc HTTP/1.1" 200 583
-[2024-02-28 16:11:26] [INFO] [django.server:212] "GET /search_suggestions/?city_name=mosco HTTP/1.1" 200 186
-[2024-02-28 16:11:27] [INFO] [django.server:212] "GET /weather/?city_name=mosco HTTP/1.1" 200 2808
-[2024-02-28 16:11:36] [INFO] [django.server:212] "GET /search_suggestions/?city_name=bij HTTP/1.1" 200 406
-[2024-02-28 16:11:37] [INFO] [django.server:212] "GET /weather/?city_name=bijar HTTP/1.1" 200 2833
-[2024-02-28 16:17:49] [INFO] [django.utils.autoreload:668] Watching for file changes with StatReloader
-[2024-02-28 16:18:05] [INFO] [django.server:212] "GET / HTTP/1.1" 200 10291
diff --git a/requirements.txt b/requirements.txt
index 1c1dbe8..9a64945 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,26 @@
-python-ldap
-python-dotenv
-django
-whitenoise
-packaging
-django-auth-ldap
-django-cors-headers
-pytz
-geopy
-requests
+# Core Dependencies for the project
+django # Django web framework
+djangorestframework #Drf
+django-redis # Redis support for Django (caching, session storage)
+django-auth-ldap # LDAP authentication for Django
+whitenoise # Static file serving for production environments
+# Environment and configuration
+python-dotenv # Load environment variables from a .env file
+python-ldap # LDAP support for Python (used by django-auth-ldap)
+
+# Date and time handling
+pytz # World timezone support
+
+# External services integration
+geopy # Geocoding (for location-based features)
+requests # HTTP library for making requests
+
+# Testing dependencies (Development only)
+pytest # Unit testing framework
+pytest-django # Django-specific test tools for pytest
+requests-mock # Mocking HTTP requests during tests
+mock # Mock Module
+
+# Utility
+packaging # Tools for dealing with Python package versions and distribution
diff --git a/weather/__pycache__/__init__.cpython-310.pyc b/weather/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index a016e0c..0000000
Binary files a/weather/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/weather/__pycache__/admin.cpython-310.pyc b/weather/__pycache__/admin.cpython-310.pyc
deleted file mode 100644
index ee99f4b..0000000
Binary files a/weather/__pycache__/admin.cpython-310.pyc and /dev/null differ
diff --git a/weather/__pycache__/apps.cpython-310.pyc b/weather/__pycache__/apps.cpython-310.pyc
deleted file mode 100644
index 60c6e14..0000000
Binary files a/weather/__pycache__/apps.cpython-310.pyc and /dev/null differ
diff --git a/weather/__pycache__/models.cpython-310.pyc b/weather/__pycache__/models.cpython-310.pyc
deleted file mode 100644
index 05837aa..0000000
Binary files a/weather/__pycache__/models.cpython-310.pyc and /dev/null differ
diff --git a/weather/__pycache__/urls.cpython-310.pyc b/weather/__pycache__/urls.cpython-310.pyc
deleted file mode 100644
index 6108eb4..0000000
Binary files a/weather/__pycache__/urls.cpython-310.pyc and /dev/null differ
diff --git a/weather/__pycache__/views.cpython-310.pyc b/weather/__pycache__/views.cpython-310.pyc
deleted file mode 100644
index 0651ce3..0000000
Binary files a/weather/__pycache__/views.cpython-310.pyc and /dev/null differ
diff --git a/weather/migrations/__pycache__/__init__.cpython-310.pyc b/weather/migrations/__pycache__/__init__.cpython-310.pyc
deleted file mode 100644
index f25c0d5..0000000
Binary files a/weather/migrations/__pycache__/__init__.cpython-310.pyc and /dev/null differ
diff --git a/weather/views.py b/weather/views.py
deleted file mode 100644
index c3f9d7e..0000000
--- a/weather/views.py
+++ /dev/null
@@ -1,249 +0,0 @@
-from django.shortcuts import render , redirect
-from django.http import HttpResponse
-from django.http import JsonResponse
-from geopy.geocoders import Nominatim
-from geopy.exc import GeocoderUnavailable
-from datetime import datetime
-from dotenv import load_dotenv
-import json
-import pytz
-import requests
-import os
-
-load_dotenv()
-API_KEY = os.getenv('API_KEY')
-
-def get_news(API_KEY, query, count=5):
- news_url = f'https://newsapi.org/v2/everything?q={query}&apiKey={API_KEY}&pageSize={count}'
- response = requests.get(news_url)
-
- if response.status_code == 200:
- news_data = response.json()
- articles = news_data.get('articles', [])
-
- news_list = []
- for article in articles:
- title = article.get('title', 'N/A')
- description = article.get('description', 'N/A')
- url = article.get('url', '#')
-
- news_list.append({
- 'title': title,
- 'description': description,
- 'url': url,
- })
- return news_list
- else:
- print(f"Failed to fetch news for {query}. Status code: {response.status_code}")
- print(response.text)
- return None
-
-
-
-def home(request):
- API_KEY = os.getenv('API_KEY')
- cities = ['London', 'Toronto', 'Dubai', 'Tehran', 'New York', 'Los Angeles']
- weather_news_list = []
- paris_url = f'http://api.openweathermap.org/data/2.5/weather?q=Paris&appid={API_KEY}'
- paris_response = requests.get(paris_url)
-
- if paris_response.status_code == 200:
- paris_data = paris_response.json()
- city_name = paris_data['name']
- weather_description = paris_data['weather'][0]['description']
- temperature = kelvin_to_celsius(paris_data['main']['temp'])
- feels_like = kelvin_to_celsius(paris_data['main']['feels_like'])
- humidity = paris_data['main']['humidity']
- wind_speed = paris_data['wind']['speed']
- paris_weather_summary = f"""
- ## Current Weather in {city_name}:
-
- * Description: {weather_description}
- * Temperature: {temperature:.2f}°C
- * Feels Like: {feels_like:.2f}°C
- * Humidity: {humidity}%
- * Wind Speed: {wind_speed:.2f} m/s
- """
- else:
- paris_weather_summary = "Failed to fetch weather data for Paris."
-
- geolocator = Nominatim(user_agent="your_app_name")
- try:
- ip_response = requests.get('https://httpbin.org/ip')
- user_ip = ip_response.json()['origin']
- location = geolocator.geocode(user_ip, timeout=50)
- except GeocoderUnavailable as e:
- print(f"GeocoderUnavailable: {e}")
- location = None
-
- if location:
- latitude = location.latitude
- longitude = location.longitude
-
- user_location_url = f'http://api.openweathermap.org/data/2.5/weather?lat={latitude}&lon={longitude}&appid={API_KEY}'
- user_location_response = requests.get(user_location_url)
-
- if user_location_response.status_code == 200:
- user_location_data = user_location_response.json()
- city_name = user_location_data['name']
- weather_description = user_location_data['weather'][0]['description']
- temperature = kelvin_to_celsius(user_location_data['main']['temp'])
- feels_like = kelvin_to_celsius(user_location_data['main']['feels_like'])
- humidity = user_location_data['main']['humidity']
- wind_speed = user_location_data['wind']['speed']
- user_location_summary = f"""
- ## Current Weather in {city_name}:
-
- * Description: {weather_description}
- * Temperature: {temperature:.2f}°C
- * Feels Like: {feels_like:.2f}°C
- * Humidity: {humidity}%
- * Wind Speed: {wind_speed:.2f} m/s
- """
- else:
- user_location_summary = "Failed to fetch weather data for your location."
- else:
- user_location_summary = "Location permission is not granted or unavailable."
-
-
- for city in cities:
- api_url = f'http://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}'
- response = requests.get(api_url)
-
- print(response.text)
-
- if response.status_code == 200:
- weather_data = response.json()
- weather_description = weather_data['weather'][0]['description']
- weather_news_list.append(f"Current weather in {city}: {weather_description}.")
- else:
- error_message = f"Failed to fetch weather data for {city}. Status code: {response.status_code}"
- print(error_message)
- weather_news_list.append(error_message)
-
-
- print(weather_news_list)
-
- tornado_news = get_news('74ea19f185cf45d99bcb73417986ac1e', 'tornado')
- storm_news = get_news('74ea19f185cf45d99bcb73417986ac1e', 'storm')
- flood_news = get_news('74ea19f185cf45d99bcb73417986ac1e', 'flood')
-
- return render(request, 'home.html', context={
- 'weather_news_list': weather_news_list,
- 'paris_weather_summary': paris_weather_summary,
- 'user_location_summary': user_location_summary,
- 'tornado_news': tornado_news,
- 'storm_news': storm_news,
- 'flood_news': flood_news,
- })
-
-def weather(request):
- city_name = request.GET.get('city_name', '')
-
- if city_name:
-
- API_KEY = os.getenv('API_KEY')
- weather_url = f'http://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={API_KEY}'
- time_url = f'http://worldtimeapi.org/api/timezone/Europe/{city_name}.json'
-
- try:
-
- weather_response = requests.get(weather_url)
- weather_data = weather_response.json()
-
- time_response = requests.get(time_url)
- time_data = time_response.json()
-
- temperature = weather_data.get('main', {}).get('temp', '')
- description = weather_data.get('weather', [{}])[0].get('description', '')
- icon = weather_data.get('weather', [{}])[0].get('icon', '')
- wind_speed = weather_data.get('wind', {}).get('speed', '')
- humidity = weather_data.get('main', {}).get('humidity', '')
-
-
- city_timezone = time_data.get('timezone', '')
- city_time_utc = time_data.get('utc_datetime', '')
-
- if city_timezone and city_time_utc:
- utc_time = datetime.strptime(city_time_utc, '%Y-%m-%dT%H:%M:%S.%fZ')
- city_time = utc_time.astimezone(pytz.timezone(city_timezone)).strftime('%Y-%m-%d %H:%M:%S')
- else:
- city_time = ''
-
-
- rainy_weather = 'rain' in description.lower()
- sunny_weather = 'clear' in description.lower() or 'sun' in description.lower()
- snowy_weather = 'snow' in description.lower()
- tornado_weather = 'tornado' in description.lower()
- cloudy_weather = 'cloud' in description.lower()
- windy_weather = 'wind' in description.lower()
- moon_weather = 'moon' in description.lower()
-
- return render(request, 'weather.html', {
- 'city_name': city_name,
- 'temperature': temperature,
- 'description': description,
- 'icon': icon,
- 'city_time': city_time,
- 'wind_speed': wind_speed,
- 'humidity': humidity,
- 'rainy_weather': rainy_weather,
- 'sunny_weather': sunny_weather,
- 'snowy_weather': snowy_weather,
- 'tornado_weather': tornado_weather,
- 'cloudy_weather': cloudy_weather,
- 'windy_weather': windy_weather,
- 'moon_weather': moon_weather,
- })
-
- except Exception as e:
- error_message = f'Error fetching data: {str(e)}'
- return render(request, 'weather.html', {'error_message': error_message})
-
- return render(request, 'weather.html')
-
-
-def kelvin_to_celsius(kelvin):
- return kelvin - 273.15
-
-def search_weather(request):
- city_name = request.GET.get('city_name', '')
- API_KEY = os.getenv('API_KEY')
-
- if city_name and API_KEY:
- try:
- api_url = f'https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={API_KEY}'
- response = requests.get(api_url)
- data = response.json()
-
- if response.status_code == 200:
- weather_data = {
- 'temperature': data['main']['temp'],
- 'description': data['weather'][0]['description'],
- 'icon': data['weather'][0]['icon']
- }
- return JsonResponse({'success': True, 'weather_data': weather_data})
- else:
- return JsonResponse({'success': False, 'error': 'Unable to fetch weather data'})
- except Exception as e:
- return JsonResponse({'success': False, 'error': str(e)})
- else:
- return JsonResponse({'success': False, 'error': 'City name or API key is missing'})
-
-
-def get_user_location(request, latitude, longitude):
- return JsonResponse({"status": "success", "message": "Location received successfully."})
-
-def search_suggestions(request):
- city_name = request.GET.get('city_name', '').lower()
-
- try:
- json_path = '/home/ghost/Desktop/webProject/virtual/climate/climate/fixture/city.list.json'
-
- with open(json_path, 'r', encoding='utf-8') as file:
- cities = json.load(file)
-
- suggestions = [city['name'] for city in cities if city_name in city['name'].lower()]
- return JsonResponse({'success': True, 'suggestions': suggestions})
- except Exception as e:
- return JsonResponse({'success': False, 'error': str(e)})