commit 9aff9c5dc17ef9a84347195278834f6f0d570174 Author: mariomitte Date: Sun May 31 19:21:25 2026 +0200 prije gemini provjere diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52ca13f --- /dev/null +++ b/.gitignore @@ -0,0 +1,206 @@ +# ---> Python +# Django +staticfiles/ + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +#lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + + + +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ diff --git a/001.BACKEND/.dockerignore b/001.BACKEND/.dockerignore new file mode 100644 index 0000000..0fe958f --- /dev/null +++ b/001.BACKEND/.dockerignore @@ -0,0 +1,10 @@ +venv/ +.venv/ +__pycache__/ +*.pyc +.git/ +.gitignore +db.sqlite3 +.env +staticfiles/ +media/ diff --git a/001.BACKEND/.env.example b/001.BACKEND/.env.example new file mode 100644 index 0000000..cd78c8b --- /dev/null +++ b/001.BACKEND/.env.example @@ -0,0 +1,4 @@ +DJANGO_SECRET_KEY=16!3r_muwp9(v@05v__em_-zuvsz=@3%mo$-i8&hd!c8bbop2$ +DJANGO_DEBUG=True +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,v003-backend.captain.mitteworkspace.cloud +DJANGO_CORS_ALLOWED_ORIGINS=https://operativa.captain.mitteworkspace.cloud,https://operativa.local.mitteworkspace.cloud,http://localhost:4321,http://127.0.0.1:4321 diff --git a/001.BACKEND/.vscode/settings.json b/001.BACKEND/.vscode/settings.json new file mode 100644 index 0000000..5f5a9cd --- /dev/null +++ b/001.BACKEND/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.defaultInterpreterPath": "${workspaceFolder}/env/Scripts/python.exe", + "python.analysis.extraPaths": [ + "${workspaceFolder}" + ] +} diff --git a/001.BACKEND/Dockerfile b/001.BACKEND/Dockerfile new file mode 100644 index 0000000..8d1aa01 --- /dev/null +++ b/001.BACKEND/Dockerfile @@ -0,0 +1,50 @@ +FROM python:3.12-slim AS builder + +# Sprječava Python da zapisuje .pyc datoteke na disk +ENV PYTHONDONTWRITEBYTECODE=1 +# Sprječava Python da sprema ispis u međumemoriju (odmah vidiš logove) +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Instalacija sistemskih ovisnosti potrebnih za kompajliranje pojedinih Python paketa +# (npr. gcc i libpq-dev ako koristiš PostgreSQL) +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Nadogradnja pip-a i kopiranje requirements datoteke +RUN pip install --no-cache-dir --upgrade pip +COPY requirements.txt . + +# Instalacija paketa u lokalni direktorij unutar builder faze +RUN pip install --no-cache-dir --user -r requirements.txt + + +# --- 2. FAZA: Pokretanje (Prodajni Runtime) --- +FROM python:3.12-slim AS runner + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PATH=/root/.local/bin:$PATH + +WORKDIR /app + +# Ako koristiš PostgreSQL na produkciji, ovdje ti treba samo lagana runtime knjižnica +RUN apt-get update && apt-get install --no-install-recommends -y \ + libpq5 \ + && rm -rf /var/lib/apt/lists/* + +# Kopiramo instalirane Python pakete iz prve faze +COPY --from=builder /root/.local /root/.local +# Kopiramo ostatak izvornog koda Django aplikacije +COPY . . + +# Otvaramo port 8000 na kojem će slušati Gunicorn +EXPOSE 8000 + +# Pokretanje aplikacije produkcijskim Gunicorn serverom +# Zamijeni "core" s točnim nazivom mape u kojoj ti se nalazi wsgi.py datoteka! +CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "core.wsgi:application"] + diff --git a/001.BACKEND/core/__init__.py b/001.BACKEND/core/__init__.py new file mode 100644 index 0000000..9e0d95f --- /dev/null +++ b/001.BACKEND/core/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/001.BACKEND/core/asgi.py b/001.BACKEND/core/asgi.py new file mode 100644 index 0000000..cf099bf --- /dev/null +++ b/001.BACKEND/core/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for core project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_asgi_application() diff --git a/001.BACKEND/core/celery.py b/001.BACKEND/core/celery.py new file mode 100644 index 0000000..2d3dc4c --- /dev/null +++ b/001.BACKEND/core/celery.py @@ -0,0 +1,14 @@ +# core/celery.py +import os +from celery import Celery # 🚀 Ovdje uvoziš čistu Celery klasu + +# Postavljamo zadani Django settings modul za 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +app = Celery('core') + +# Sve postavke vezane za Celery imaju 'CELERY_' prefiks u settings.py +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Automatski učitaj tasks.py iz svih registriranih Django aplikacija +app.autodiscover_tasks() \ No newline at end of file diff --git a/001.BACKEND/core/settings.py b/001.BACKEND/core/settings.py new file mode 100644 index 0000000..9585924 --- /dev/null +++ b/001.BACKEND/core/settings.py @@ -0,0 +1,182 @@ +""" +Django settings for core project. +""" + +import os +from pathlib import Path +from datetime import timedelta +from dotenv import load_dotenv + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +load_dotenv(BASE_DIR / '.env') + +SECRET_KEY = os.getenv( + 'DJANGO_SECRET_KEY', + 'django-insecure-!228(8gy#3a7l-@_^g1s4bipj&@*+_415+ulx0^-9jw(%ksdvy', +) + +DEBUG = os.getenv('DJANGO_DEBUG', 'True').lower() in ('1', 'true', 'yes', 'on') + +# Custom user model +AUTH_USER_MODEL = 'users.CustomUser' + +ALLOWED_HOSTS = [ + host.strip() + for host in os.getenv('DJANGO_ALLOWED_HOSTS', '127.0.0.1,localhost').split(',') + if host.strip() +] + +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # plugins + 'fleet', + 'corsheaders', + 'django_filters', + 'rest_framework_simplejwt', + 'rest_framework', + # Aplikacije + 'users', + 'kupci', + 'operations', + 'kalendar', +] + +MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', # Mora biti prvi! + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'core.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'core.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + +AUTH_PASSWORD_VALIDATORS = [ + { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator' }, + { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator' }, + { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator' }, + { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator' }, +] + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'Europe/Zagreb' +USE_I18N = True +USE_TZ = True + +STATIC_URL = 'static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +if DEBUG: + DEFAULT_PERMISSION_CLASSES = [ 'rest_framework.permissions.AllowAny' ] +else: + DEFAULT_PERMISSION_CLASSES = [ 'rest_framework.permissions.IsAuthenticated' ] + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend' + ], + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': DEFAULT_PERMISSION_CLASSES +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), + 'AUTH_HEADER_TYPES': ('Bearer',), +} + +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + + +# 2. CSRF_TRUSTED_ORIGINS +# Primjer: https://app.tvojadomena.hr,https://api.tvojadomena.hr +CSRF_TRUSTED_ORIGINS = [ + origin.strip() + for origin in os.getenv('DJANGO_CSRF_TRUSTED_ORIGINS', 'http://127.0.0.1:4321,http://localhost:4321').split(',') + if origin.strip() +] + +# CORS POSTAVKE - POPRAVLJENI ZAREZI I FORMALNI ORIGINI +if DEBUG: + CORS_ALLOW_ALL_ORIGINS = True +else: + CORS_ALLOW_ALL_ORIGINS = False + CORS_ALLOWED_ORIGINS = [ + host.strip() + for host in os.getenv('DJANGO_CORS_ALLOWED_ORIGINS', '').split(',') + if host.strip() + ] + +CORS_ALLOW_METHODS = [ + "DELETE", + "GET", + "OPTIONS", + "PATCH", + "POST", + "PUT", +] + +CORS_ALLOW_HEADERS = [ + 'accept', + 'accept-encoding', + 'authorization', + 'content-type', + 'dnt', + 'origin', + 'user-agent', + 'x-csrftoken', + 'x-requested-with', + 'access-control-request-private-network', +] + +# Ako preglednik pošalje PNA preflight zahtjev sa lokalne mreže, +# corsheaders će ga s ovim u potpunosti odobriti bez redirecta +CORS_ALLOW_PRIVATE_NETWORK = True +CORS_PREFLIGHT_MAX_AGE = 86400 + + +# Celery Postavke +CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0") +CELERY_RESULT_BACKEND = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0") +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'Europe/Zagreb' \ No newline at end of file diff --git a/001.BACKEND/core/urls.py b/001.BACKEND/core/urls.py new file mode 100644 index 0000000..c2711c6 --- /dev/null +++ b/001.BACKEND/core/urls.py @@ -0,0 +1,44 @@ +""" +URL configuration for core project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/6.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.conf import settings +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import path, include +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, +) + +urlpatterns = [ + # Admin panel + path('admin/', admin.site.urls), + # API endpointi + path('api/kupci/', include('kupci.urls')), + path('api/fleet/', include('fleet.urls')), + path('api/operativa/', include('operations.urls')), + path('api/kalendar/', include('kalendar.urls')), + path('api/users/', include('users.urls')), + # Endpoint za dobivanje tokena (Login) + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + # Endpoint za osvježavanje tokena (da se ne mora stalno logirati) + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), +] + +# Dodajemo pristup media datotekama samo u development fazi +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/001.BACKEND/core/wsgi.py b/001.BACKEND/core/wsgi.py new file mode 100644 index 0000000..6d36530 --- /dev/null +++ b/001.BACKEND/core/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for core project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + +application = get_wsgi_application() diff --git a/001.BACKEND/fleet/__init__.py b/001.BACKEND/fleet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/fleet/admin.py b/001.BACKEND/fleet/admin.py new file mode 100644 index 0000000..6fa7e6d --- /dev/null +++ b/001.BACKEND/fleet/admin.py @@ -0,0 +1,90 @@ +from django.contrib import admin +from django.urls import reverse +from django.utils.safestring import mark_safe +from .models import Stroj, Vozilo +from operations.models import RadniNalog, PutniNalog # Importamo modele iz druge aplikacije + +# --- INLINE KLASE --- + +class PutniNalogInline(admin.TabularInline): + """Prikazuje putne naloge unutar pregleda vozila.""" + model = PutniNalog + extra = 0 + fields = ('broj_naloga', 'datum_izdavanja', 'korisnik', 'pocetna_km', 'zavrsna_km', 'status') + readonly_fields = ('broj_naloga', 'datum_izdavanja', 'korisnik', 'pocetna_km', 'zavrsna_km', 'status') + can_delete = False + verbose_name = "Povijest putovanja" + verbose_name_plural = "Povijest putovanja za ovo vozilo" + +class RadniNalogInline(admin.TabularInline): + model = RadniNalog + extra = 0 + # Umjesto običnog polja 'broj_naloga', koristimo metodu 'link_na_nalog' + fields = ('link_na_nalog', 'datum_kreiranja', 'klijent', 'izvrsitelj', 'status') + readonly_fields = ('link_na_nalog', 'datum_kreiranja', 'klijent', 'izvrsitelj', 'status') + can_delete = False + + def link_na_nalog(self, obj): + if obj.id: + # Generiramo URL za admin promjenu Radnog Naloga + url = reverse('admin:operations_radninalog_change', args=[obj.id]) + return mark_safe(f'RN-{obj.broj_naloga}') + return "-" + + link_na_nalog.short_description = 'Broj naloga' + +# --- GLAVNE ADMIN KLASE --- + +@admin.register(Vozilo) +class VoziloAdmin(admin.ModelAdmin): + list_display = ('naziv', 'registracija', 'status', 'pocetni_kilometri', 'trenutni_kilometri') + search_fields = ('naziv', 'registracija') + list_filter = ('status',) + ordering = ('naziv',) + + # Dodajemo inline za putne naloge + inlines = [PutniNalogInline] + +@admin.register(Stroj) +class StrojAdmin(admin.ModelAdmin): + list_display = ( + 'serijski_broj', + 'prikaz_naziva', + 'vlasnik', + 'tip', + 'radni_sati', + 'registracija', + 'datum_zadnjeg_atesta' + ) + + search_fields = ( + 'serijski_broj', + 'naziv', + 'marka', + 'model_stroja', + 'vlasnik__naziv', + 'vlasnik__oib' + ) + + list_filter = ('tip', 'marka', 'vlasnik') + autocomplete_fields = ['vlasnik'] + + # Dodajemo inline za radne naloge + inlines = [RadniNalogInline] + + fieldsets = ( + ('Osnovne informacije', { + 'fields': ('vlasnik', 'naziv', 'tip', 'serijski_broj') + }), + ('Detalji o modelu', { + 'fields': (('marka', 'model_stroja'), 'godina_proizvodnje') + }), + ('Tehnički podaci i održavanje', { + 'fields': ('radni_sati', 'registracija', 'datum_zadnjeg_atesta'), + 'classes': ('collapse',) + }), + ) + + def prikaz_naziva(self, obj): + return f"{obj.marka} {obj.model_stroja}".strip() or obj.naziv + prikaz_naziva.short_description = 'Marka i Model' \ No newline at end of file diff --git a/001.BACKEND/fleet/apps.py b/001.BACKEND/fleet/apps.py new file mode 100644 index 0000000..a955ebd --- /dev/null +++ b/001.BACKEND/fleet/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class FleetConfig(AppConfig): + name = 'fleet' diff --git a/001.BACKEND/fleet/management/commands/populate_stroj.py b/001.BACKEND/fleet/management/commands/populate_stroj.py new file mode 100644 index 0000000..ff18900 --- /dev/null +++ b/001.BACKEND/fleet/management/commands/populate_stroj.py @@ -0,0 +1,103 @@ +import random +from datetime import date, timedelta +from django.core.management.base import BaseCommand +from faker import Faker +from fleet.models import Stroj +from kupci.models import Kupac + +class Command(BaseCommand): + help = 'Popunjava bazu podataka isključivo Liebherr strojevima sinkronizirano s logistikom flote' + + def add_arguments(self, parser): + parser.add_argument( + 'total', + type=int, + help='Broj Liebherr strojeva koje želiš kreirati', + default=10 + ) + + def handle(self, *args, **kwargs): + total = kwargs['total'] + fake = Faker(['hr_HR']) + kupci = list(Kupac.objects.all()) + + if not kupci: + self.stdout.write(self.style.ERROR("Greška: Nema kupaca u bazi. Prvo pokreni populate_kupci!")) + return + + # Bogatiji katalog usklađen s tvorničkim Liebherr oznakama i tvojim TIP_STROJA choices + liebherr_katalog = { + 'dizalica_toranj': [ + '132 EC-H 8 Litronic', + '280 EC-H 12', + '81 K.1', + '172 EC-B 8', + '125 K' + ], + 'dizalica_auto': [ + 'LTM 1030-2.1', + 'LTM 1050-3.1', + 'LTM 1060-3.1', + 'LTM 1120-4.1', + 'LTM 1350-6.1', # Dodan LTM 1350 (sukladno planiranim modifikacijama ventila) + 'LTC 1050-3.1' + ] + } + + self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Generiram {total} Liebherr strojeva u bazu...')) + + created_count = 0 + for _ in range(total): + tip = random.choice(['dizalica_toranj', 'dizalica_auto']) + model_naziv = random.choice(liebherr_katalog[tip]) + + # Odabiremo nasumičnog vlasnika za ovaj stroj + vlasnik_stroja = random.choice(kupci) + + # Realistični Liebherr tvornički serijski brojevi (npr. LE-054321) + serijski = f"LE-{fake.numerify(text='0######')}" + + # USKLAĐENO: Registracija za autodizalice sada ima ispravne razmake i crticu + # Koristimo reg. oznaku koja odgovara sjedištu vlasnika ili nasumični HR grad + reg = None + if tip == 'dizalica_auto': + gradovi = ['ZG', 'KA', 'RI', 'ST', 'OS', 'KR'] + grad = random.choice(gradovi) + reg_broj = fake.numerify(text='####') + reg_slova = fake.bothify(text='??', letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ') + reg = f"{grad} {reg_broj}-{reg_slova}" + + # Radni sati dizalice + sati = random.uniform(800.0, 15000.0) if tip == 'dizalica_toranj' else random.uniform(200.0, 6000.0) + + # Atesti unutar zadnjih godinu dana + datum_atesta = fake.date_between(start_date='-1y', end_date='today') + + # USKLAĐENO: Generiramo lokaciju. Ako klijent/vlasnik ima definiran grad, + # stroj se primarno nalazi na gradilištu u tom gradu (ili okolici), inače koristimo fake.city() + lokacija_stroja = vlasnik_stroja.grad if (vlasnik_stroja and hasattr(vlasnik_stroja, 'grad')) else fake.city() + + # Nasumična simulacija aktivnih gradilišta u regiji + if random.random() > 0.6: + lokacija_stroja = f"Gradilište {lokacija_stroja}" + + try: + Stroj.objects.create( + vlasnik=vlasnik_stroja, + naziv=f"Liebherr {model_naziv}", + serijski_broj=serijski, + marka='Liebherr', + model_stroja=model_naziv, + godina_proizvodnje=random.randint(2012, 2025), + tip=tip, + radni_sati=round(sati, 1), # Zaokružujemo na 1 decimalu sukladno formama + registracija=reg, + datum_zadnjeg_atesta=datum_atesta + ) + created_count += 1 + self.stdout.write(f" ✅ Kreiran: Liebherr {model_naziv} (Vlasnik: {vlasnik_stroja.naziv})") + except Exception as e: + self.stderr.write(f" ❌ Greška kod S/N {serijski}: {e}") + continue + + self.stdout.write(self.style.SUCCESS(f'\nGotovo! Uspješno dodano {created_count} Liebherr strojeva u bazu.')) \ No newline at end of file diff --git a/001.BACKEND/fleet/management/commands/populate_vozila.py b/001.BACKEND/fleet/management/commands/populate_vozila.py new file mode 100644 index 0000000..92c8c51 --- /dev/null +++ b/001.BACKEND/fleet/management/commands/populate_vozila.py @@ -0,0 +1,86 @@ +import random +from django.core.management.base import BaseCommand +from django.core.exceptions import ValidationError +from django.db import IntegrityError +from faker import Faker +from fleet.models import Vozilo + +class Command(BaseCommand): + help = 'Popunjava bazu podataka s gospodarskim vozilima sinkronizirano s Astro logistikom' + + def add_arguments(self, parser): + parser.add_argument( + 'total', + type=int, + help='Broj vozila koje želiš kreirati', + default=5 + ) + + def handle(self, *args, **kwargs): + total = kwargs['total'] + fake = Faker(['hr_HR']) + + # Modeli koji odgovaraju tvom voznom parku za mobilne servisne timove + modeli_podaci = [ + 'VW Transporter T6', + 'Mercedes-Benz Vito', + 'Citroen Jumper', + 'Ford Transit Custom', + 'Renault Kangoo Maxi', + 'Peugeot Boxer', + 'Opel Vivaro', + ] + + self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Kreiram {total} servisnih vozila u flotu...')) + + brojac = 0 + maksimalni_pokusaji = total * 5 + pokusaji = 0 + + while brojac < total and pokusaji < maksimalni_pokusaji: + pokusaji += 1 + model_naziv = random.choice(modeli_podaci) + + # Generiranje realistične registracije u formatu: ZG 1234-AB + gradovi = ['ZG', 'KR', 'KA', 'ST', 'RI', 'OS', 'VZ', 'PU'] + grad = random.choice(gradovi) + + reg_broj = fake.numerify(text=random.choice(['###', '####'])) + reg_slova = fake.bothify(text=random.choice(['?', '??']), letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ') + registracija = f"{grad} {reg_broj}-{reg_slova}" + + # Kilometraža: Trenutni kilometri moraju pratiti povijest korištenja vozila + pocetni = random.randint(10000, 220000) + dodatni = random.randint(1500, 45000) + trenutni = pocetni + dodatni + + # POPRAVLJENO: Koristimo mala slova sukladno tvojim Choice ograničenjima u modelu + status_izbor = random.choice(['aktivan', 'aktivan', 'servis', 'neaktivan']) + + try: + # Provjeravamo postoji li već ova registracija u bazi unaprijed + if Vozilo.objects.filter(registracija=registracija).exists(): + continue + + vozilo = Vozilo.objects.create( + naziv=model_naziv, + registracija=registracija, + pocetni_kilometri=pocetni, + trenutni_kilometri=trenutni, + status=status_izbor + ) + brojac += 1 + self.stdout.write(f" ✅ Kreirano: {vozilo.naziv} [{vozilo.registracija}] | Status: {status_izbor} ({brojac}/{total})") + + except IntegrityError: + # Ako baza ipak prijavi duplikat registracije unutar transakcije, samo idemo dalje + continue + except ValidationError as ve: + # Ako se pojavi neka druga validacijska greška, ispiši je da znamo o čemu se radi + self.stderr.write(f" ⚠️ Validacijska greška za {model_naziv}: {ve}") + continue + except Exception as e: + self.stderr.write(f" ❌ Kritična greška pri upisu vozila: {e}") + break + + self.stdout.write(self.style.SUCCESS(f'\nGotovo! Uspješno dodano {brojac} servisnih vozila u bazu flote.')) \ No newline at end of file diff --git a/001.BACKEND/fleet/migrations/0001_initial.py b/001.BACKEND/fleet/migrations/0001_initial.py new file mode 100644 index 0000000..5cd2b4c --- /dev/null +++ b/001.BACKEND/fleet/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# Generated by Django 6.0.5 on 2026-05-22 03:53 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('kupci', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Vozilo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('naziv', models.CharField(max_length=50, verbose_name='Interni naziv')), + ('registracija', models.CharField(db_index=True, max_length=15, unique=True)), + ('pocetni_kilometri', models.PositiveIntegerField(default=0)), + ('trenutni_kilometri', models.PositiveIntegerField(default=0)), + ('status', models.CharField(choices=[('aktivan', 'Aktivan'), ('servis', 'Na Servisu'), ('neaktivan', 'Izvan Pogona')], default='aktivan', max_length=20)), + ], + options={ + 'verbose_name': 'Vozilo', + 'verbose_name_plural': 'Vozila', + 'ordering': ['naziv'], + }, + ), + migrations.CreateModel( + name='Stroj', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('naziv', models.CharField(help_text='Npr. Liebherr LTM 1030', max_length=100)), + ('serijski_broj', models.CharField(max_length=50, unique=True)), + ('marka', models.CharField(blank=True, max_length=50)), + ('model_stroja', models.CharField(blank=True, max_length=50)), + ('godina_proizvodnje', models.PositiveIntegerField(blank=True, null=True)), + ('tip', models.CharField(choices=[('dizalica_toranj', 'Toranjska dizalica'), ('dizalica_auto', 'Autodizalica'), ('vilicar', 'Viličar'), ('platforma', 'Radna platforma')], max_length=30)), + ('radni_sati', models.DecimalField(decimal_places=2, default=0, max_digits=12)), + ('registracija', models.CharField(blank=True, help_text='Za autodizalice i vozila', max_length=20, null=True)), + ('datum_zadnjeg_atesta', models.DateField(blank=True, null=True)), + ('vlasnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='strojevi', to='kupci.kupac', verbose_name='Vlasnik/Kupac')), + ], + options={ + 'verbose_name': 'Stroj', + 'verbose_name_plural': 'Strojevi', + 'ordering': ['marka', 'model_stroja'], + }, + ), + ] diff --git a/001.BACKEND/fleet/migrations/__init__.py b/001.BACKEND/fleet/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/fleet/models.py b/001.BACKEND/fleet/models.py new file mode 100644 index 0000000..ae25873 --- /dev/null +++ b/001.BACKEND/fleet/models.py @@ -0,0 +1,88 @@ +from django.db import models +from django.core.exceptions import ValidationError + + +class Vozilo(models.Model): + STATUS_CHOICES = [ + ('aktivan', 'Aktivan'), + ('servis', 'Na Servisu'), + ('neaktivan', 'Izvan Pogona'), + ] + + naziv = models.CharField(max_length=50, verbose_name="Interni naziv") + registracija = models.CharField(max_length=15, unique=True, db_index=True) + + # Kilometri + pocetni_kilometri = models.PositiveIntegerField(default=0) + trenutni_kilometri = models.PositiveIntegerField(default=0) + + status = models.CharField( + max_length=20, + choices=STATUS_CHOICES, + default='aktivan' + ) + + class Meta: + verbose_name = "Vozilo" + verbose_name_plural = "Vozila" + ordering = ['naziv'] + + def clean(self): + # Osnovna logika: trenutni kilometri ne smiju biti manji od početnih + if self.trenutni_kilometri < self.pocetni_kilometri: + raise ValidationError({ + 'trenutni_kilometri': "Trenutni kilometri ne mogu biti manji od početnih." + }) + + def save(self, *args, **kwargs): + # Logika za nove zapise + if not self.pk: + self.trenutni_kilometri = self.pocetni_kilometri + + self.registracija = self.registracija.upper().replace(" ", "") + + # Pozivamo clean() prije spremanja (Django to ne radi automatski u save()) + self.full_clean() + super().save(*args, **kwargs) + + def __str__(self): + return f"{self.naziv} ({self.registracija})" + +class Stroj(models.Model): + TIP_STROJA = [ + ('dizalica_toranj', 'Toranjska dizalica'), + ('dizalica_auto', 'Autodizalica'), + ('vilicar', 'Viličar'), + ('platforma', 'Radna platforma'), + ] + + # Poveznica s kupcem: Jedan kupac može imati mnogo strojeva + # --- RJEŠENJE ZA LOOP: Koristimo string 'kupci.Kupac' --- + vlasnik = models.ForeignKey( + 'kupci.Kupac', + on_delete=models.CASCADE, + related_name='strojevi', + verbose_name="Vlasnik/Kupac" + ) + + naziv = models.CharField(max_length=100, help_text="Npr. Liebherr LTM 1030") + serijski_broj = models.CharField(max_length=50, unique=True) + marka = models.CharField(max_length=50, blank=True) + model_stroja = models.CharField(max_length=50, blank=True) + godina_proizvodnje = models.PositiveIntegerField(null=True, blank=True) + tip = models.CharField(max_length=30, choices=TIP_STROJA) + + # Podaci za održavanje + radni_sati = models.DecimalField(max_digits=12, decimal_places=2, default=0) + registracija = models.CharField(max_length=20, blank=True, null=True, help_text="Za autodizalice i vozila") + datum_zadnjeg_atesta = models.DateField(null=True, blank=True) + + class Meta: + verbose_name = "Stroj" + verbose_name_plural = "Strojevi" + ordering = ['marka', 'model_stroja'] + + def __str__(self): + # Mala nadogradnja da ne vraća prazan string ako marka/model nedostaju + oznaka = f"{self.marka} {self.model_stroja}".strip() or self.naziv + return f"{oznaka} (S/N: {self.serijski_broj})" \ No newline at end of file diff --git a/001.BACKEND/fleet/serializers.py b/001.BACKEND/fleet/serializers.py new file mode 100644 index 0000000..90169c7 --- /dev/null +++ b/001.BACKEND/fleet/serializers.py @@ -0,0 +1,115 @@ +from rest_framework import serializers +from .models import Stroj, Vozilo + + +class StrojShortSerializer(serializers.ModelSerializer): + """ + Kraća verzija serijalizatora za brze liste ili dropdown izbornike. + """ + class Meta: + model = Stroj + fields = ['id', 'naziv', 'serijski_broj', 'registracija'] + +class StrojSerializer(serializers.ModelSerializer): + """ + Glavni serializer za strojeve/dizalice. + """ + # Prikazuje naziv vlasnika (kupca) umjesto samo ID-a + # (source='vlasnik.naziv' jer je u modelu ForeignKey nazvan 'vlasnik') + vlasnik_naziv = serializers.ReadOnlyField(source='vlasnik.naziv') + + # get_tip_display() pretvara 'dizalica_auto' u 'Autodizalica' (čitljivije za UI) + tip_human_readable = serializers.CharField(source='get_tip_display', read_only=True) + + radni_nalozi = serializers.SerializerMethodField() + + class Meta: + model = Stroj + fields = [ + 'id', + 'vlasnik', + 'vlasnik_naziv', + 'naziv', + 'marka', + 'model_stroja', + 'serijski_broj', + 'tip', + 'tip_human_readable', + 'godina_proizvodnje', + 'radni_sati', + 'registracija', + 'datum_zadnjeg_atesta', + 'radni_nalozi', # Novo polje + ] + + def get_radni_nalozi(self, obj): + # Lokalni import rješava kružni import error s operations aplikacijom + from operations.serializers import RadniNalogSerializer + + # Dohvaćamo radne naloge i sortiramo ih tako da najnoviji budu prvi + queryset = obj.radni_nalozi.all().order_by('-datum_kreiranja') + + # Vraćamo serijalizirane podatke + return RadniNalogSerializer(queryset, many=True, context=self.context).data + +class VoziloSerializer(serializers.ModelSerializer): + """ + Osnovni serializer koji se koristi za Create/Update operacije. + """ + class Meta: + model = Vozilo + fields = '__all__' + +class VoziloListaSerializer(serializers.ModelSerializer): + """ + Serializer za brzi prikaz u tablicama. + """ + # Ljudski čitljiv status (npr. 'Na Servisu' umjesto 'servis') + status_prikaz = serializers.CharField(source='get_status_display', read_only=True) + + class Meta: + model = Vozilo + fields = ['id', 'naziv', 'registracija', 'trenutni_kilometri', 'status', 'status_prikaz'] + +class VoziloListaSerializer(serializers.ModelSerializer): + """ + Serializer za brzi prikaz u tablicama. + """ + # Ljudski čitljiv status (npr. 'Na Servisu' umjesto 'servis') + status_prikaz = serializers.CharField(source='get_status_display', read_only=True) + + class Meta: + model = Vozilo + fields = ['id', 'naziv', 'registracija', 'trenutni_kilometri', 'status', 'status_prikaz'] + +class VoziloDetaljiSerializer(serializers.ModelSerializer): + """ + Serializer s dodatnom statistikom za detaljni prikaz vozila. + """ + ukupno_predeno = serializers.SerializerMethodField() + status_prikaz = serializers.CharField(source='get_status_display', read_only=True) + + putni_nalozi = serializers.SerializerMethodField() + + class Meta: + model = Vozilo + fields = [ + 'id', + 'naziv', + 'registracija', + 'pocetni_kilometri', + 'trenutni_kilometri', + 'ukupno_predeno', + 'status', + 'status_prikaz', + 'putni_nalozi', + ] + + def get_ukupno_predeno(self, obj): + """Izračunava razliku između trenutnih i početnih kilometara""" + return obj.trenutni_kilometri - obj.pocetni_kilometri + + def get_putni_nalozi(self, obj): + from operations.serializers import PutniNalogSerializer + queryset = obj.putni_nalozi.all() + return PutniNalogSerializer(queryset, many=True, context=self.context).data \ No newline at end of file diff --git a/001.BACKEND/fleet/tests.py b/001.BACKEND/fleet/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/001.BACKEND/fleet/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/001.BACKEND/fleet/urls.py b/001.BACKEND/fleet/urls.py new file mode 100644 index 0000000..8ecdca2 --- /dev/null +++ b/001.BACKEND/fleet/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import StrojViewSet, VoziloViewSet + +router = DefaultRouter() +router.register(r'strojevi', StrojViewSet, basename='stroj') +router.register(r'vozila', VoziloViewSet, basename='vozilo') + +urlpatterns = [ + path('', include(router.urls)), +] \ No newline at end of file diff --git a/001.BACKEND/fleet/views.py b/001.BACKEND/fleet/views.py new file mode 100644 index 0000000..9535f6c --- /dev/null +++ b/001.BACKEND/fleet/views.py @@ -0,0 +1,72 @@ +from rest_framework import viewsets, filters +from django_filters.rest_framework import DjangoFilterBackend +from .models import Stroj, Vozilo +from .serializers import ( + VoziloSerializer, + VoziloListaSerializer, + VoziloDetaljiSerializer, + StrojSerializer +) + +class StrojViewSet(viewsets.ModelViewSet): + """ + ViewSet za pregled, kreiranje i uređivanje strojeva i dizalica. + """ + # select_related radi SQL JOIN i povlači podatke o kupcu odmah + queryset = Stroj.objects.all().select_related('vlasnik').prefetch_related('radni_nalozi') + serializer_class = StrojSerializer + + # Dodajemo mogućnosti filtriranja, pretrage i sortiranja + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter + ] + + # Omogućuje filtriranje strojeva po vlasniku ili tipu + # npr. /api/strojevi/?vlasnik=1 ili /api/strojevi/?tip=dizalica_auto + filterset_fields = ['vlasnik', 'tip'] + + # Omogućuje pretragu po nazivu, marki ili serijskom broju + search_fields = ['naziv', 'marka', 'serijski_broj', 'registracija'] + + # Omogućuje sortiranje po radnim satima ili godini proizvodnje + ordering_fields = ['radni_sati', 'godina_proizvodnje', 'datum_zadnjeg_atesta'] + ordering = ['-datum_zadnjeg_atesta'] # Defaultno prikazuje one koji su zadnji atestirani + +class VoziloViewSet(viewsets.ModelViewSet): + """ + ViewSet za pregled, kreiranje i uređivanje voznog parka. + Usklađen sa StrojViewSet arhitekturom. + """ + queryset = Vozilo.objects.all() + + # Postavljanje filter backenda (isto kao u StrojViewSet) + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter + ] + + # Omogućuje filtriranje vozila po statusu + # npr. /api/fleet/vozila/?status=u_radu + filterset_fields = ['status'] + + # Omogućuje pretragu po nazivu i registraciji + search_fields = ['naziv', 'registracija'] + + # Omogućuje sortiranje po kilometrima ili statusu + ordering_fields = ['trenutni_kilometri', 'status'] + ordering = ['naziv'] # Defaultno sortiranje po abecedi naziva + + def get_serializer_class(self): + """ + Dinamički odabir serializera za optimalne performanse. + """ + if self.action == 'list': + return VoziloListaSerializer + if self.action == 'retrieve': + return VoziloDetaljiSerializer + + # Default za POST/PUT/PATCH + return VoziloSerializer \ No newline at end of file diff --git a/001.BACKEND/kalendar/__init__.py b/001.BACKEND/kalendar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kalendar/admin.py b/001.BACKEND/kalendar/admin.py new file mode 100644 index 0000000..0d6c311 --- /dev/null +++ b/001.BACKEND/kalendar/admin.py @@ -0,0 +1,81 @@ +from django.contrib import admin +from django.utils.html import format_html +from .models import Dogadaj + +@admin.register(Dogadaj) +class DogadajAdmin(admin.ModelAdmin): + # 1. Postavke liste (Dashboard pregled) + list_display = ('prikaz_naslova', 'tip_ikona', 'pocetak', 'kraj', 'get_izvrsitelj', 'link_na_rn') + list_filter = ('tip', 'pocetak', 'serviser_u_kalendaru') + search_fields = ('naslov', 'opis', 'radni_nalog__broj_naloga', 'klijent__naziv') + ordering = ('-pocetak',) + + # 2. Optimizacija baze (Spriječava N+1 problem zbog @property-ja) + def get_queryset(self, request): + return super().get_queryset(request).select_related( + 'radni_nalog__izvrsitelj', + 'serviser_u_kalendaru', + 'klijent', + 'vozilo' + ) + + # --- CUSTOM METODE ZA PRIKAZ --- + + def prikaz_naslova(self, obj): + """Bojanje naslova prema tipu za brzu vizualnu orijentaciju""" + boje = { + 'servis': '#2980b9', # Plava + 'isporuka': '#27ae60', # Zelena + 'sastanak': '#f39c12', # Narančasta + 'biljeska': '#7f8c8d', # Siva + } + boja = boje.get(obj.tip, 'black') + return format_html('{}', boja, obj.naslov) + prikaz_naslova.short_description = "Naslov" + + def tip_ikona(self, obj): + """Dodaje malu ikonu uz tip događaja""" + ikone = { + 'servis': '🛠️', + 'isporuka': '🏗️', + 'sastanak': '🤝', + 'biljeska': '📝', + } + return format_html('{} {}', ikone.get(obj.tip, '📅'), obj.get_tip_display()) + tip_ikona.short_description = "Tip" + + def get_izvrsitelj(self, obj): + """Prikazuje izvršitelja koristeći logiku iz model @property""" + korisnik = obj.izvrsitelj + if korisnik: + return korisnik.get_full_name() or korisnik.username + return format_html('Nije dodijeljeno') + get_izvrsitelj.short_description = "Izvršitelj (Serviser)" + + def link_na_rn(self, obj): + """Prikazuje broj radnog naloga ako postoji""" + if obj.radni_nalog: + return format_html('#{}', obj.radni_nalog.broj_naloga) + return "-" + link_na_rn.short_description = "Radni Nalog" + + # 3. Organizacija polja u formi (Fieldsets) + fieldsets = ( + ('Glavne informacije', { + 'fields': (('naslov', 'tip'), 'opis') + }), + ('Vrijeme održavanja', { + 'fields': (('pocetak', 'kraj'),) + }), + ('Poveznice sustava', { + 'fields': ('radni_nalog', 'klijent', 'vozilo'), + 'description': "Povežite događaj s klijentom ili radnim nalogom za bolju evidenciju." + }), + ('Odgovornost', { + 'fields': ('serviser_u_kalendaru',), + 'description': "NAPOMENA: Ako je povezan Radni Nalog, sustav ignorira ovo polje i koristi servisera s naloga." + }), + ) + + # Postavljanje radnog naloga kao readonly ako želiš spriječiti ručno mijenjanje veza + # readonly_fields = ('radni_nalog',) \ No newline at end of file diff --git a/001.BACKEND/kalendar/apps.py b/001.BACKEND/kalendar/apps.py new file mode 100644 index 0000000..4e8731c --- /dev/null +++ b/001.BACKEND/kalendar/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class KalendarConfig(AppConfig): + name = 'kalendar' diff --git a/001.BACKEND/kalendar/migrations/0001_initial.py b/001.BACKEND/kalendar/migrations/0001_initial.py new file mode 100644 index 0000000..019583c --- /dev/null +++ b/001.BACKEND/kalendar/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 6.0.5 on 2026-05-22 03:53 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('fleet', '0001_initial'), + ('kupci', '0001_initial'), + ('operations', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Dogadaj', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('naslov', models.CharField(max_length=200)), + ('opis', models.TextField(blank=True)), + ('tip', models.CharField(choices=[('servis', 'Radni Nalog / Servis'), ('isporuka', 'Isporuka Stroja'), ('sastanak', 'Sastanak s kupcem'), ('biljeska', 'Interna bilješka')], default='biljeska', max_length=20)), + ('pocetak', models.DateTimeField()), + ('kraj', models.DateTimeField()), + ('datum_kreiranja', models.DateTimeField(auto_now_add=True)), + ('datum_azuriranja', models.DateTimeField(auto_now=True)), + ('klijent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='kupci.kupac')), + ('radni_nalog', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kalendar_termin', to='operations.radninalog')), + ('serviser_u_kalendaru', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dodatni_zadaci', to=settings.AUTH_USER_MODEL)), + ('vozilo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='fleet.vozilo')), + ], + options={ + 'verbose_name': 'Kalendar', + 'verbose_name_plural': 'Kalendar', + 'ordering': ['pocetak'], + }, + ), + ] diff --git a/001.BACKEND/kalendar/migrations/__init__.py b/001.BACKEND/kalendar/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kalendar/models.py b/001.BACKEND/kalendar/models.py new file mode 100644 index 0000000..5dfcba6 --- /dev/null +++ b/001.BACKEND/kalendar/models.py @@ -0,0 +1,61 @@ +from django.db import models +from django.conf import settings + +class Dogadaj(models.Model): + TIP_DOGADAJA = [ + ('servis', 'Radni Nalog / Servis'), + ('isporuka', 'Isporuka Stroja'), + ('sastanak', 'Sastanak s kupcem'), + ('biljeska', 'Interna bilješka'), + ] + + naslov = models.CharField(max_length=200) + opis = models.TextField(blank=True) + tip = models.CharField(max_length=20, choices=TIP_DOGADAJA, default='biljeska') + + pocetak = models.DateTimeField() + kraj = models.DateTimeField() + + # Automatsko praćenje izmjena samog događaja u kalendaru + datum_kreiranja = models.DateTimeField(auto_now_add=True) + datum_azuriranja = models.DateTimeField(auto_now=True) + + # Veze + klijent = models.ForeignKey('kupci.Kupac', on_delete=models.SET_NULL, null=True, blank=True) + vozilo = models.ForeignKey('fleet.Vozilo', on_delete=models.SET_NULL, null=True, blank=True) + radni_nalog = models.OneToOneField( + 'operations.RadniNalog', + on_delete=models.CASCADE, + null=True, + blank=True, + related_name='kalendar_termin' + ) + + # Serviser koji je dodijeljen direktno u kalendaru (za sastanke/bilješke) + # Ako postoji radni_nalog, koristit ćemo izvrsitelja s naloga + serviser_u_kalendaru = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name='dodatni_zadaci' + ) + + class Meta: + verbose_name = "Kalendar" + verbose_name_plural = "Kalendar" + ordering = ['pocetak'] + + @property + def izvrsitelj(self): + """ + Pametna metoda koja vraća servisera: + 1. Ako je termin vezan za Radni Nalog, uzmi njegovog izvršitelja. + 2. Ako nije, uzmi servisera upisanog u kalendar. + """ + if self.radni_nalog: + return self.radni_nalog.izvrsitelj + return self.serviser_u_kalendaru + + def __str__(self): + return f"{self.naslov} - {self.izvrsitelj}" \ No newline at end of file diff --git a/001.BACKEND/kalendar/serializers.py b/001.BACKEND/kalendar/serializers.py new file mode 100644 index 0000000..0de53f6 --- /dev/null +++ b/001.BACKEND/kalendar/serializers.py @@ -0,0 +1,77 @@ +from rest_framework import serializers +from .models import Dogadaj +import re + +class DogadajSerializer(serializers.ModelSerializer): + # 1. Mapiranje za standardne kalendarske knjižnice + title = serializers.CharField(source='naslov') + start = serializers.DateTimeField(source='pocetak') + end = serializers.DateTimeField(source='kraj') + + # 2. Dinamički podaci (korištenje @property-ja i Choice polja) + izvrsitelj_ime = serializers.SerializerMethodField() + tip_display = serializers.CharField(source='get_tip_display', read_only=True) + + # 3. Dodatne informacije za UI + boja = serializers.SerializerMethodField() + je_radni_nalog = serializers.SerializerMethodField() + + # 4. NOVA RAZDVOJENA POLJA ZA VOCILO I ČISTI OPIS KWARTA + vozilo_naziv = serializers.SerializerMethodField() + opis_cisti = serializers.SerializerMethodField() + + class Meta: + model = Dogadaj + fields = [ + 'id', 'title', 'start', 'end', 'tip', 'tip_display', + 'opis', 'opis_cisti', 'vozilo_naziv', 'izvrsitelj_ime', + 'boja', 'je_radni_nalog', 'klijent', 'vozilo', 'radni_nalog' + ] + + def get_izvrsitelj_ime(self, obj): + """Dohvaća ime preko tvog @property izvrsitelj u modelu""" + korisnik = obj.izvrsitelj + if korisnik: + return korisnik.get_full_name() or korisnik.username + return "Nije dodijeljeno" + + def get_boja(self, obj): + """Dodjeljuje boju ovisno o tipu događaja""" + boje = { + 'servis': '#3498db', # Plava + 'isporuka': '#2ecc71', # Zelena + 'sastanak': '#f1c40f', # Žuta + 'biljeska': '#95a5a6', # Siva + } + return boje.get(obj.tip, '#34495e') + + def get_je_radni_nalog(self, obj): + """Pomaže frontendu da zna treba li prikazati link na radni nalog""" + return obj.radni_nalog is not None + + def _parsiraj_spojeni_opis(self, obj): + """Pomoćna metoda koja analizira tekst i vraća (vozilo, opis)""" + sirovi_tekst = obj.opis or "" + + # Ako tekst sadrži ključne riječi, razdvajamo ga pomoću Regexa na backendu + if "Vozilo:" in sirovi_tekst or "Opis kvara:" in sirovi_tekst: + vozilo_match = re.search(r"Vozilo:\s*(.*?)(?=\s*Opis kvara:|$)", sirovi_tekst, re.IGNORECASE) + opis_match = re.search(r"Opis kvara:\s*(.*)", sirovi_tekst, re.IGNORECASE) + + vozilo = vozilo_match.group(1).strip() if vozilo_match else None + opis = opis_match.group(1).strip() if opis_match else sirovi_tekst + return vozilo, opis + + # Ako je polje vozilo na modelu već popunjeno kao relacija/tekst, iskoristi ga + model_vozilo = str(obj.vozilo) if obj.vozilo else None + return model_vozilo, sirovi_tekst + + def get_vozilo_naziv(self, obj): + """Vraća samo čisti naziv vozila""" + vozilo, _ = self._parsiraj_spojeni_opis(obj) + return vozilo + + def get_opis_cisti(self, obj): + """Vraća samo čisti tekst opisa kvara""" + _, opis = self._parsiraj_spojeni_opis(obj) + return opis if opis else "Nema dodatnog opisa." \ No newline at end of file diff --git a/001.BACKEND/kalendar/services.py b/001.BACKEND/kalendar/services.py new file mode 100644 index 0000000..ecf8ceb --- /dev/null +++ b/001.BACKEND/kalendar/services.py @@ -0,0 +1,52 @@ +from django.utils import timezone +from datetime import timedelta +from .models import Dogadaj + +def kreiraj_kalendarski_unos(radni_nalog): + """ + Kreira termin u kalendaru na temelju novog radnog naloga. + """ + pocetak = timezone.now() + kraj = pocetak + timedelta(hours=2) + + # DOHVAĆANJE VOZILA: Budući da vozilo više nije na Radnom nalogu, + # moramo ga izvući iz povezanog Putnog naloga (ako postoji). + vozilo_obj = None + vozilo_str = "Nije definirano" + + if radni_nalog.putni_nalog and radni_nalog.putni_nalog.vozilo: + vozilo_obj = radni_nalog.putni_nalog.vozilo + vozilo_str = str(vozilo_obj) + + # Kreiramo Dogadaj + dogadaj = Dogadaj.objects.create( + naslov=f"RN: {radni_nalog.broj_naloga} | {radni_nalog.klijent.naziv}", + opis=f"Vozilo: {vozilo_str}\nOpis kvara: {radni_nalog.opis_kvara}", + tip='servis', + pocetak=pocetak, + kraj=kraj, + klijent=radni_nalog.klijent, + # Ovdje spremamo instancu vozila u polje modela Dogadaj + vozilo=vozilo_obj, + radni_nalog=radni_nalog, + serviser_u_kalendaru=radni_nalog.izvrsitelj + ) + + return dogadaj + +def azuriraj_kalendarski_unos(radni_nalog): + """ + Sinkronizira podatke u kalendaru koristeći metodu modela. + """ + try: + # Pokušaj pronaći postojeći događaj preko related_name 'kalendar_termin' + dogadaj = radni_nalog.kalendar_termin + dogadaj.sync_with_radni_nalog() + except Exception: + # Ako ne postoji (npr. nalog je stariji od uvođenja kalendara), kreiraj ga + from .services import kreiraj_kalendarski_unos + kreiraj_kalendarski_unos(radni_nalog) + +def obrisi_kalendarski_unos(radni_nalog): + if hasattr(radni_nalog, 'kalendar_termin'): + radni_nalog.kalendar_termin.delete() \ No newline at end of file diff --git a/001.BACKEND/kalendar/tests.py b/001.BACKEND/kalendar/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/001.BACKEND/kalendar/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/001.BACKEND/kalendar/urls.py b/001.BACKEND/kalendar/urls.py new file mode 100644 index 0000000..37b4ab6 --- /dev/null +++ b/001.BACKEND/kalendar/urls.py @@ -0,0 +1,17 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import DogadajViewSet, MojRasporedView + +# Kreiramo router i registriramo naš ViewSet za kalendarske događaje +router = DefaultRouter() +router.register(r'dogadaji', DogadajViewSet, basename='dogadaji') + +# Definiramo URL obrasce +urlpatterns = [ + # Sve CRUD rute koje router generira (npr. /kalendar/dogadaji/) + path('', include(router.urls)), + + # Specijalna ruta za "današnji raspored" servisera + # Dostupna na: /kalendar/moj-raspored/ + path('moj-raspored/', MojRasporedView.as_view(), name='moj-raspored'), +] \ No newline at end of file diff --git a/001.BACKEND/kalendar/views.py b/001.BACKEND/kalendar/views.py new file mode 100644 index 0000000..23799b1 --- /dev/null +++ b/001.BACKEND/kalendar/views.py @@ -0,0 +1,55 @@ +from rest_framework import viewsets, generics, filters +from django_filters.rest_framework import DjangoFilterBackend +from django.utils import timezone +from django.db.models import Q +from .models import Dogadaj +from .serializers import DogadajSerializer + +class DogadajViewSet(viewsets.ModelViewSet): + """ + Glavni API za kalendar. Podržava pregled svih termina, + drag-and-drop (patch) i kreiranje novih bilješki. + """ + # VAŽNO: select_related('radni_nalog__izvrsitelj') je ključan + # jer tvoj model koristi @property izvrsitelj. + # Bez ovoga, svaki termin u kalendaru bi radio poseban upit u bazu. + queryset = Dogadaj.objects.all().select_related( + 'radni_nalog__izvrsitelj', + 'radni_nalog__klijent', + 'klijent', + 'vozilo', + 'serviser_u_kalendaru' + ) + serializer_class = DogadajSerializer + + filter_backends = [DjangoFilterBackend, filters.OrderingFilter] + filterset_fields = ['tip', 'klijent', 'vozilo'] + ordering = ['pocetak'] + + def get_queryset(self): + """ + Opcionalno: Ako želiš da serviseri vide samo svoje termine u kalendaru, + otkomentiraj donji dio. Ako dispečer vidi sve, ostavi kako jest. + """ + qs = super().get_queryset() + # if not self.request.user.is_staff: + # return qs.filter(Q(radni_nalog__izvrsitelj=self.request.user) | Q(serviser_u_kalendaru=self.request.user)) + return qs + +class MojRasporedView(generics.ListAPIView): + """ + Endpoint namijenjen mobilnoj aplikaciji: "Moji zadaci za danas". + Pristup: GET /kalendar/api/moj-raspored/ + """ + serializer_class = DogadajSerializer + + def get_queryset(self): + user = self.request.user + danas = timezone.now().date() + + # Filtriramo samo ono što je dodijeljeno ulogiranom korisniku za danas + return Dogadaj.objects.filter( + Q(pocetak__date=danas) | Q(kraj__date=danas) + ).filter( + Q(radni_nalog__izvrsitelj=user) | Q(serviser_u_kalendaru=user) + ).select_related('radni_nalog', 'klijent', 'vozilo') \ No newline at end of file diff --git a/001.BACKEND/kupci/__init__.py b/001.BACKEND/kupci/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kupci/admin.py b/001.BACKEND/kupci/admin.py new file mode 100644 index 0000000..cb1f403 --- /dev/null +++ b/001.BACKEND/kupci/admin.py @@ -0,0 +1,41 @@ +from django.contrib import admin +from .models import Kupac + +@admin.register(Kupac) +class KupacAdmin(admin.ModelAdmin): + # Polja koja se vide u glavnoj tablici + list_display = ('naziv', 'oib', 'grad', 'telefon', 'prikaz_broja_strojeva', 'aktivno') + + # Brzo uređivanje "aktivno" statusa direktno iz tablice + list_editable = ('aktivno',) + + # Filtriranje s desne strane + list_filter = ('tip', 'aktivno', 'grad') + + # Pretraga po ključnim poljima + # OBAVEZNO dodaj search_fields kako bi autocomplete radio u fleet i nalozi + search_fields = ('naziv', 'oib', 'email', 'grad') + + # Organizacija forme za unos + fieldsets = ( + ('Osnovni podaci', { + 'fields': (('naziv', 'tip'), 'oib') + }), + ('Kontakt i lokacija', { + 'fields': ('email', 'telefon', 'adresa', ('grad', 'postanski_broj')) + }), + ('Dodatno', { + 'fields': ('napomena', 'aktivno'), + 'classes': ('collapse',) + }), + ) + + # Čitanje sistemskih podataka bez mogućnosti izmjene + readonly_fields = ('datum_kreiranja',) + + def prikaz_broja_strojeva(self, obj): + """Prikazuje broj strojeva direktno u listi kupaca""" + # Koristimo property koji si već definirao u modelu + return obj.broj_strojeva + + prikaz_broja_strojeva.short_description = 'Strojevi' \ No newline at end of file diff --git a/001.BACKEND/kupci/apps.py b/001.BACKEND/kupci/apps.py new file mode 100644 index 0000000..3ebc1f1 --- /dev/null +++ b/001.BACKEND/kupci/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class KupciConfig(AppConfig): + name = 'kupci' diff --git a/001.BACKEND/kupci/management/__init__.py b/001.BACKEND/kupci/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kupci/management/commands/__init__.py b/001.BACKEND/kupci/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kupci/management/commands/hello_world.py b/001.BACKEND/kupci/management/commands/hello_world.py new file mode 100644 index 0000000..f2786ed --- /dev/null +++ b/001.BACKEND/kupci/management/commands/hello_world.py @@ -0,0 +1,8 @@ +from typing import Any +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + + def handle(self, *args: Any, **options: Any): + print("hello world") \ No newline at end of file diff --git a/001.BACKEND/kupci/management/commands/populate_kupci.py b/001.BACKEND/kupci/management/commands/populate_kupci.py new file mode 100644 index 0000000..5d6fb67 --- /dev/null +++ b/001.BACKEND/kupci/management/commands/populate_kupci.py @@ -0,0 +1,75 @@ +import random +from django.core.management.base import BaseCommand +from faker import Faker +from kupci.models import Kupac + +class Command(BaseCommand): + help = 'Popunjava bazu podataka s testnim kupcima spremnim za logistički modul' + + def add_arguments(self, parser): + parser.add_argument( + 'total', + type=int, + help='Broj kupaca koje želiš kreirati', + default=10 + ) + + def handle(self, *args, **kwargs): + total = kwargs['total'] + fake = Faker(['hr_HR']) + + self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Kreiram {total} testnih kupaca...')) + + # Kontrolirani skup gradova u nominativu za savršen ispis relacija i odredišta u logistici + hr_gradovi = [ + {'grad': 'Zagreb', 'pbr': '10000'}, + {'grad': 'Karlovac', 'pbr': '47000'}, + {'grad': 'Ozalj', 'pbr': '47280'}, + {'grad': 'Split', 'pbr': '21000'}, + {'grad': 'Rijeka', 'pbr': '51000'}, + {'grad': 'Osijek', 'pbr': '31000'}, + {'grad': 'Varaždin', 'pbr': '42000'}, + {'grad': 'Zadar', 'pbr': '23000'}, + {'grad': 'Velika Gorica', 'pbr': '10410'}, + {'grad': 'Sisak', 'pbr': '44000'} + ] + + created_count = 0 + for _ in range(total): + tip = random.choice(['pravno', 'fizicko']) + + if tip == 'pravno': + naziv = fake.company() + else: + naziv = fake.name() + + # Generiramo OIB (točno 11 znamenki u string formatu) + oib = "".join([str(random.randint(0, 9)) for _ in range(11)]) + + # Odabiremo grad i poštanski broj u paru kako bi podaci imali smisla + lokacija = random.choice(hr_gradovi) + + # Generiramo telefonski broj koji je čist i siguran za tel: linkove u Astru + pozivni = random.randint(1, 9) + telefon = f"+385 {pozivni} {random.randint(100, 999)}-{random.randint(1000, 9999)}" + + try: + # Upotrebljavamo create, a jedinstvenost OIB-a hvatamo u iznimci + Kupac.objects.create( + naziv=naziv, + oib=oib, + email=fake.email(), + telefon=telefon, + adresa=fake.street_address(), + grad=lokacija['grad'], # Usklađeno: čisti nominativ (npr. 'Karlovac') + postanski_broj=lokacija['pbr'], # Usklađeno: odgovarajući poštanski broj + tip=tip, + napomena="Automatski generiran testni kupac usklađen s modulom operativne logistike.", + aktivno=True + ) + created_count += 1 + except Exception as e: + # Ako OIB slučajno kolidira (unutar unique=True), preskoči i nastavi generirati sljedećeg + continue + + self.stdout.write(self.style.SUCCESS(f'✅ Uspješno dodano {created_count} novih kupaca u bazu!')) \ No newline at end of file diff --git a/001.BACKEND/kupci/migrations/0001_initial.py b/001.BACKEND/kupci/migrations/0001_initial.py new file mode 100644 index 0000000..12a1327 --- /dev/null +++ b/001.BACKEND/kupci/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 6.0.5 on 2026-05-22 03:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Kupac', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('naziv', models.CharField(help_text='Ime i prezime ili puni naziv tvrtke', max_length=255)), + ('oib', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='OIB')), + ('email', models.EmailField(blank=True, max_length=254)), + ('telefon', models.CharField(blank=True, max_length=50)), + ('adresa', models.CharField(blank=True, max_length=255)), + ('grad', models.CharField(blank=True, max_length=100)), + ('postanski_broj', models.CharField(blank=True, max_length=10)), + ('tip', models.CharField(choices=[('pravno', 'Pravna osoba (Tvrtka)'), ('fizicko', 'Fizička osoba')], default='pravno', max_length=10)), + ('napomena', models.TextField(blank=True, help_text='Interni podaci o kupcu')), + ('datum_kreiranja', models.DateTimeField(auto_now_add=True)), + ('aktivno', models.BooleanField(default=True)), + ], + options={ + 'verbose_name': 'Kupac', + 'verbose_name_plural': 'Kupci', + 'ordering': ['naziv'], + }, + ), + ] diff --git a/001.BACKEND/kupci/migrations/__init__.py b/001.BACKEND/kupci/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/kupci/models.py b/001.BACKEND/kupci/models.py new file mode 100644 index 0000000..7652087 --- /dev/null +++ b/001.BACKEND/kupci/models.py @@ -0,0 +1,49 @@ +from django.db import models + +class Kupac(models.Model): + TIP_KUPCA = [ + ('pravno', 'Pravna osoba (Tvrtka)'), + ('fizicko', 'Fizička osoba'), + ] + + naziv = models.CharField( + max_length=255, + help_text="Ime i prezime ili puni naziv tvrtke" + ) + oib = models.CharField( + max_length=11, + unique=True, + null=True, + blank=True, + verbose_name="OIB" + ) + email = models.EmailField(blank=True) + telefon = models.CharField(max_length=50, blank=True) + adresa = models.CharField(max_length=255, blank=True) + grad = models.CharField(max_length=100, blank=True) + postanski_broj = models.CharField(max_length=10, blank=True) + + tip = models.CharField( + max_length=10, + choices=TIP_KUPCA, + default='pravno' + ) + + napomena = models.TextField(blank=True, help_text="Interni podaci o kupcu") + + # Sistemski podaci + datum_kreiranja = models.DateTimeField(auto_now_add=True) + aktivno = models.BooleanField(default=True) + + class Meta: + verbose_name = "Kupac" + verbose_name_plural = "Kupci" + ordering = ['naziv'] + + def __str__(self): + return self.naziv + + @property + def broj_strojeva(self): + """Vraća broj strojeva povezanih s ovim kupcem""" + return self.strojevi.count() \ No newline at end of file diff --git a/001.BACKEND/kupci/serializers.py b/001.BACKEND/kupci/serializers.py new file mode 100644 index 0000000..8b6b4b4 --- /dev/null +++ b/001.BACKEND/kupci/serializers.py @@ -0,0 +1,73 @@ +from rest_framework import serializers +from .models import Kupac +from fleet.models import Stroj + +from fleet.serializers import StrojSerializer + +class KupacSerializer(serializers.ModelSerializer): + """ + Serializer za listu kupaca. + Fokus je na brzini i osnovnim kontaktnim podacima. + """ + # Izračunato polje iz modela (@property broj_strojeva) + broj_strojeva = serializers.IntegerField(read_only=True) + + class Meta: + model = Kupac + fields = [ + 'id', + 'naziv', + 'grad', + 'telefon', + 'email', + 'tip', + 'aktivno', + 'broj_strojeva' # Korisno za prikaz u tablici (npr. "5 strojeva") + ] + +class KupacDetaljiSerializer(serializers.ModelSerializer): + # Ugniježđeni strojevi - prikazujemo puni objekt, ne samo ID + strojevi = StrojSerializer(many=True, read_only=True) + + # Dodatna polja koja nisu u bazi, ali su korisna za Astro frontend + ukupan_broj_strojeva = serializers.IntegerField(source='broj_strojeva', read_only=True) + + class Meta: + model = Kupac + fields = [ + 'id', + 'naziv', + 'oib', + 'email', + 'telefon', + 'adresa', + 'grad', + 'postanski_broj', + 'tip', + 'napomena', + 'aktivno', + 'datum_kreiranja', + 'ukupan_broj_strojeva', + 'strojevi' # Lista svih strojeva s punim detaljima + ] + +class KupacListaSerializer(serializers.ModelSerializer): + """ + Optimizirani serializer za prikaz u tablici (listi). + Vraća samo osnovne podatke potrebne za pregled svih klijenata. + """ + # Dohvaćamo property iz modela koji broji strojeve + broj_strojeva = serializers.IntegerField(read_only=True) + + class Meta: + model = Kupac + fields = [ + 'id', + 'naziv', + 'grad', + 'oib', + 'telefon', + 'tip', + 'aktivno', + 'broj_strojeva' + ] \ No newline at end of file diff --git a/001.BACKEND/kupci/tests.py b/001.BACKEND/kupci/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/001.BACKEND/kupci/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/001.BACKEND/kupci/urls.py b/001.BACKEND/kupci/urls.py new file mode 100644 index 0000000..e393e85 --- /dev/null +++ b/001.BACKEND/kupci/urls.py @@ -0,0 +1,14 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import KupacViewSet + +# Kreiramo router i registriramo naš ViewSet +# 'r' ispred stringa označava raw string (dobra praksa za regex/putanje) +router = DefaultRouter() +router.register(r'svi', KupacViewSet, basename='kupac') + +# Definiramo URL obrasce +urlpatterns = [ + # Sve rute koje router generira (npr. /api/kupci/ i /api/kupci/{id}/) + path('', include(router.urls)), +] \ No newline at end of file diff --git a/001.BACKEND/kupci/views.py b/001.BACKEND/kupci/views.py new file mode 100644 index 0000000..1f62578 --- /dev/null +++ b/001.BACKEND/kupci/views.py @@ -0,0 +1,35 @@ +from rest_framework import viewsets, filters +from django_filters.rest_framework import DjangoFilterBackend +from .models import Kupac +from .serializers import KupacSerializer, KupacDetaljiSerializer, KupacListaSerializer + +class KupacViewSet(viewsets.ModelViewSet): + """ + API endpoint koji omogućuje pregled, pretragu i uređivanje kupaca. + """ + # Optimizacija: prefetch_related smanjuje broj upita jer odmah vuče strojeve + queryset = Kupac.objects.all().prefetch_related('strojevi') + serializer_class = KupacSerializer + + # Dodajemo filter backende + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter + ] + + # 1. Točno filtriranje (npr. ?tip=pravno) + filterset_fields = ['tip', 'aktivno', 'grad'] + + # 2. Pretraga (npr. ?search=gradnja) + # Pretražuje naziv ili OIB + search_fields = ['naziv', 'oib'] + + # 3. Sortiranje (npr. ?ordering=-datum_kreiranja) + ordering_fields = ['naziv', 'datum_kreiranja'] + ordering = ['naziv'] # Defaultno sortiranje + + def get_serializer_class(self): + if self.action == 'retrieve': # Ako se gleda pojedinačni kupac + return KupacDetaljiSerializer # Onaj s ugniježđenim strojevima + return KupacListaSerializer # Obični bez strojeva \ No newline at end of file diff --git a/001.BACKEND/manage.py b/001.BACKEND/manage.py new file mode 100644 index 0000000..f2a662c --- /dev/null +++ b/001.BACKEND/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0_d3agRoR.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0_d3agRoR.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/galerija/gal_RN-2026-0001_0_d3agRoR.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0001/rn_RN-2026-0001_Lxitqya.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0_5NspH7M.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0_5NspH7M.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_0_5NspH7M.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_1.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_1.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/galerija/gal_RN-2026-0002_1.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0002/rn_RN-2026-0002_IF7DXsc.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0_NU3o34G.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0_NU3o34G.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/galerija/gal_RN-2026-0003_0_NU3o34G.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0003/rn_RN-2026-0003_X4Rp7Zx.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0_rBSQW0V.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0_rBSQW0V.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_0_rBSQW0V.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_1.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_1.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/galerija/gal_RN-2026-0004_1.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0004/rn_RN-2026-0004_da6jIUk.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0_fpweyHe.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0_fpweyHe.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_0_fpweyHe.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_1.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_1.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/galerija/gal_RN-2026-0005_1.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg new file mode 100644 index 0000000..fc0e460 Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005.jpg differ diff --git a/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg b/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/media/radni-nalozi/RN-2026-0005/rn_RN-2026-0005_AUBF0x7.jpg differ diff --git a/001.BACKEND/operations/__init__.py b/001.BACKEND/operations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/operations/admin.py b/001.BACKEND/operations/admin.py new file mode 100644 index 0000000..6db0ba7 --- /dev/null +++ b/001.BACKEND/operations/admin.py @@ -0,0 +1,112 @@ +# src/operations/admin.py +from django.contrib import admin +from django.utils.html import format_html +from .models import RadniNalog, RadniNalogSlika, PutniNalog + +# --- INLINES --- + +class RadniNalogSlikaInline(admin.TabularInline): + """Omogućuje dodavanje više slika unutar samog radnog naloga.""" + model = RadniNalogSlika + extra = 1 + fields = ('slika', 'opis') + +class RadniNalogInline(admin.StackedInline): + """ + Omogućuje dodavanje i pregled radnih naloga direktno unutar Putnog naloga. + Ovo je ključno za terenski rad (jedno putovanje -> više odrađenih strojeva). + """ + model = RadniNalog + extra = 0 + autocomplete_fields = ['stroj', 'klijent', 'izvrsitelj'] + fieldsets = ( + (None, { + 'fields': (('broj_naloga', 'status'), ('klijent', 'stroj'), 'opis_kvara') + }), + ) + +# --- ADMIN REGISTRACIJE --- + +@admin.register(PutniNalog) +class PutniNalogAdmin(admin.ModelAdmin): + list_display = ('broj_naloga', 'prikaz_vozila', 'korisnik', 'pocetna_km', 'zavrsna_km', 'status_marker') + list_filter = ('status', 'datum_izdavanja', 'vozilo') + search_fields = ('broj_naloga', 'vozilo__registracija', 'korisnik__last_name') + autocomplete_fields = ['vozilo', 'korisnik'] + + # Inlines omogućuju da vidiš servise (RN) unutar putovanja (PN) + inlines = [RadniNalogInline] + + # pocetna_km je readonly jer se automatski povlači iz flote pri kreiranju + readonly_fields = ('pocetna_km',) + + fieldsets = ( + ('Logistika puta', { + 'fields': (('broj_naloga', 'status'), ('vozilo', 'korisnik'), 'relacija', 'mjesto_odredista') + }), + ('Kilometraža i vrijeme', { + 'fields': (('pocetna_km', 'zavrsna_km'), ('vrijeme_polaska', 'vrijeme_povratka')), + 'description': 'Završni kilometri će automatski ažurirati bazu vozila po zatvaranju naloga.' + }), + ) + + def prikaz_vozila(self, obj): + return format_html('{}', obj.vozilo.registracija) + prikaz_vozila.short_description = "Vozilo" + + def status_marker(self, obj): + color = 'green' if obj.status == 'ZAVRSEN' else 'orange' + return format_html('{}', color, obj.get_status_display()) + status_marker.short_description = "Status" + + +@admin.register(RadniNalog) +class RadniNalogAdmin(admin.ModelAdmin): + # Prikazujemo stroj i putni nalog kako bismo znali gdje je servis odrađen + list_display = ('prikaz_broja', 'datum_kreiranja', 'klijent', 'stroj', 'status_boja', 'povezani_pn') + list_filter = ('status', 'datum_kreiranja', 'izvrsitelj', 'stroj__tip') + + # Pretraga po serijskom broju stroja je ključna za servisere + search_fields = ( + 'broj_naloga', + 'klijent__naziv', + 'stroj__naziv', + 'stroj__serijski_broj' + ) + + # Autocomplete za lakše biranje među stotinama strojeva i klijenata + autocomplete_fields = ['klijent', 'stroj', 'putni_nalog', 'izvrsitelj'] + + inlines = [RadniNalogSlikaInline] + + fieldsets = ( + ('Subjekt servisa', { + 'fields': (('broj_naloga', 'status'), ('klijent', 'stroj'), ('izvrsitelj', 'putni_nalog')) + }), + ('Opis i dokumentacija', { + 'fields': ('opis_kvara', 'slika_kvara', 'potpis_klijenta') + }), + ) + + def prikaz_broja(self, obj): + return format_html('RN-{}', obj.broj_naloga) + prikaz_broja.short_description = "Nalog" + + def povezani_pn(self, obj): + if obj.putni_nalog: + return obj.putni_nalog.broj_naloga + return "Nema (Radiona)" + povezani_pn.short_description = "Putni nalog" + + def status_boja(self, obj): + boje = { + 'PLANIRANO': '#6b7280', + 'U_RADU': '#2563eb', + 'ZAVRSENO': '#10b981', + } + return format_html( + '{}', + boje.get(obj.status, 'black'), + obj.get_status_display() + ) + status_boja.short_description = "Status" \ No newline at end of file diff --git a/001.BACKEND/operations/apps.py b/001.BACKEND/operations/apps.py new file mode 100644 index 0000000..3f55e15 --- /dev/null +++ b/001.BACKEND/operations/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class OperationsConfig(AppConfig): + name = 'operations' diff --git a/001.BACKEND/operations/management/commands/populate_radninalog.py b/001.BACKEND/operations/management/commands/populate_radninalog.py new file mode 100644 index 0000000..b42f098 --- /dev/null +++ b/001.BACKEND/operations/management/commands/populate_radninalog.py @@ -0,0 +1,161 @@ +import random +import os +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +from django.utils import timezone +from django.core.files import File +from django.db import transaction +from operations.models import RadniNalog, RadniNalogSlika, PutniNalog, BrojacSekvence +from fleet.models import Vozilo, Stroj +from kupci.models import Kupac +from kalendar.services import kreiraj_kalendarski_unos + +User = get_user_model() + +class Command(BaseCommand): + help = 'Popunjava bazu s Putnim nalozima, Radnim nalozima i slikama sinkronizirano sa sekvencama' + + def add_arguments(self, parser): + parser.add_argument('total_putovanja', type=int, nargs='?', default=3, help='Broj putnih naloga') + parser.add_argument('--img', type=str, default='test_assets/kvar.jpg') + + def handle(self, *args, **kwargs): + total_putovanja = kwargs['total_putovanja'] + test_img_path = kwargs['img'] + + kupci = Kupac.objects.prefetch_related('strojevi').all() + vozila = list(Vozilo.objects.all()) + serviseri = list(User.objects.filter(is_staff=True)) + + if not kupci.exists() or not vozila or not serviseri: + self.stdout.write(self.style.ERROR("Baza mora imati kupce, vozila i servisere (is_staff=True).")) + return + + # Provjera slike + if not os.path.exists(test_img_path): + self.stdout.write(self.style.WARNING(f"Testna slika {test_img_path} nije pronađena. Generiram bez slika.")) + test_img_path = None + + opisi_kvarova = [ + "Godišnji periodični servis dizalice.", + "Zamjena hidrauličnog crijeva i dopuna ulja.", + "Kalibracija senzora opterećenja.", + "Popravak daljinskog upravljača.", + "Hitna intervencija: Stroj u kvaru." + ] + + self.stdout.write(f'🚀 Generiranje {total_putovanja} putovanja sinkronizirano sa sekvencama...') + success_count_rn = 0 + trenutna_godina = timezone.now().year + + # Definiramo ključ i prefikse za putne naloge + prefix_pn = f"PN-{trenutna_godina}-" + kljuc_sekvence_rn = f"radni_nalog_{trenutna_godina}" + + for i in range(total_putovanja): + serviser = random.choice(serviseri) + vozilo = random.choice(vozila) + + # 1. GENERIRANJE NEUNIŠTIVOG BROJA PUTNOG NALOGA + # Da skripta ne bi udarila u unique=True, tražimo stvarni zadnji PN u bazi + with transaction.atomic(): + zadnji_pn = PutniNalog.objects.select_for_update().filter( + broj_naloga__startswith=prefix_pn + ).order_by('-broj_naloga').first() + + if zadnji_pn: + try: + zadnji_broj_pn = int(zadnji_pn.broj_naloga.split('-')[-1]) + novi_broj_pn = zadnji_broj_pn + 1 + except (ValueError, IndexError): + novi_broj_pn = PutniNalog.objects.count() + 1 + else: + novi_broj_pn = 1 + + broj_pn = f"{prefix_pn}{novi_broj_pn:04d}" + + # Kreiraj PUTNI NALOG + putni_nalog = PutniNalog.objects.create( + broj_naloga=broj_pn, + vozilo=vozilo, + korisnik=serviser, + relacija="Zagreb - Teren - Zagreb", + mjesto_odredista="Teren", + vrijeme_polaska=timezone.now() + ) + + self.stdout.write(self.style.MIGRATE_LABEL(f"Otvoren {broj_pn} (Vozilo: {vozilo.registracija})")) + + # 2. Kreiraj 1 do 3 RADNA NALOGA za ovaj putni nalog + za_odraditi = random.randint(1, 3) + kreirani_radni_nalozi = [] + + for j in range(za_odraditi): + klijenti_sa_strojima = [k for k in kupci if k.strojevi.exists()] + if not klijenti_sa_strojima: + continue + klijent = random.choice(klijenti_sa_strojima) + stroj = random.choice(list(klijent.strojevi.all())) + + try: + # Inicijaliziramo objekt bez broja_naloga kako bi se aktivirala + # naša neuništiva sekvencijska logika unutar save() metode modela! + nalog = RadniNalog( + klijent=klijent, + stroj=stroj, + putni_nalog=putni_nalog, + opis_kvara=random.choice(opisi_kvarova), + izvrsitelj=serviser, + status=RadniNalog.StatusRadaChoices.PLANIRANO + ) + + # Prvo moramo izvršiti osnovni save da model kroz BrojacSekvence dodijeli čisti broj + nalog.save() + + # Ako slika postoji, dodajemo je NAKON što je broj kreiran (da putanja_slike_naloga ima ispravan folder) + if test_img_path: + with open(test_img_path, 'rb') as f: + nalog.slika_kvara.save(f'rn_{nalog.broj_naloga}.jpg', File(f), save=True) + + kreirani_radni_nalozi.append(nalog) + + # Dodatne slike u galeriju + if test_img_path: + for k in range(random.randint(1, 2)): + with open(test_img_path, 'rb') as f: + RadniNalogSlika.objects.create( + radni_nalog=nalog, + slika=File(f, name=f'gal_{nalog.broj_naloga}_{k}.jpg'), + opis=f"Detalj kvara {k+1}" + ) + + # 5. Upis u kalendar + try: + kreiraj_kalendarski_unos(nalog) + except Exception as cal_err: + self.stdout.write(self.style.WARNING(f" ⚠️ Kalendar unos preskočen: {cal_err}")) + + success_count_rn += 1 + self.stdout.write(f" ✅ {nalog.broj_naloga} uspješno povezan u {broj_pn}") + + except Exception as e: + self.stderr.write(f" ❌ Greška kod kreiranja RN: {e}") + + # 3. NASUMIČNO ZATVARANJE (Simulacija završenog posla) + if random.random() > 0.5 and kreirani_radni_nalozi: + try: + # Prvo moramo zatvoriti sve radne naloge da bi PN prošao validaciju + for rn in kreirani_radni_nalozi: + rn.status = RadniNalog.StatusRadaChoices.ZAVRSENO + rn.save() + + # Dodajemo završne kilometre (uvijek više od početnih) + putni_nalog.zavrsna_km = putni_nalog.pocetna_km + random.randint(50, 500) + putni_nalog.status = PutniNalog.StatusChoices.ZAVRSEN + putni_nalog.save() + + self.stdout.write(self.style.SUCCESS(f" 🏁 {broj_pn} automatski ZATVOREN. Kilometri vozila ažurirani.")) + except Exception as e: + self.stdout.write(self.style.WARNING(f" ⚠️ PN-{broj_pn} nije zatvoren: {e}")) + + self.stdout.write(self.style.SUCCESS(f'\nGotovo! Generirano {total_putovanja} PN i {success_count_rn} RN.')) \ No newline at end of file diff --git a/001.BACKEND/operations/migrations/0001_initial.py b/001.BACKEND/operations/migrations/0001_initial.py new file mode 100644 index 0000000..e6a1427 --- /dev/null +++ b/001.BACKEND/operations/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 6.0.5 on 2026-05-22 03:53 + +import django.db.models.deletion +import django.utils.timezone +import operations.models +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('fleet', '__first__'), + ('kupci', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='BrojacSekvence', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('naziv_sekvence', models.CharField(max_length=50, unique=True)), + ('zadnji_broj', models.PositiveIntegerField(default=0)), + ], + options={ + 'verbose_name': 'Brojač sekvence', + 'verbose_name_plural': 'Brojači sekvenci', + }, + ), + migrations.CreateModel( + name='PutniNalog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('broj_naloga', models.CharField(max_length=50, unique=True)), + ('datum_izdavanja', models.DateField(default=django.utils.timezone.now)), + ('relacija', models.CharField(max_length=255)), + ('mjesto_odredista', models.CharField(max_length=255)), + ('pocetna_km', models.PositiveIntegerField(editable=False, help_text='Automatski preuzeto iz modela Vozilo')), + ('zavrsna_km', models.PositiveIntegerField(blank=True, null=True)), + ('vrijeme_polaska', models.DateTimeField(default=django.utils.timezone.now)), + ('vrijeme_povratka', models.DateTimeField(blank=True, null=True)), + ('status', models.CharField(choices=[('OTVOREN', 'Otvoren'), ('ZAVRSEN', 'Završen')], default='OTVOREN', max_length=15)), + ('korisnik', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('vozilo', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='putni_nalozi', to='fleet.vozilo', verbose_name='Službeno vozilo')), + ], + options={ + 'verbose_name': 'Putni nalog', + 'verbose_name_plural': 'Putni nalozi', + }, + ), + migrations.CreateModel( + name='RadniNalog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('broj_naloga', models.CharField(blank=True, max_length=20, unique=True)), + ('opis_kvara', models.TextField()), + ('status', models.CharField(choices=[('PLANIRANO', 'Planirano'), ('U_RADU', 'U radu'), ('ZAVRSENO', 'Završen')], default='PLANIRANO', max_length=20)), + ('datum_kreiranja', models.DateTimeField(auto_now_add=True, verbose_name='Datum kreiranja')), + ('datum_azuriranja', models.DateTimeField(auto_now=True, verbose_name='Zadnja izmjena')), + ('slika_kvara', models.ImageField(blank=True, null=True, upload_to=operations.models.putanja_slike_naloga)), + ('potpis_klijenta', models.ImageField(blank=True, null=True, upload_to='potpisi/%Y/%m/')), + ('izvrsitelj', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dodijeljeni_nalozi', to=settings.AUTH_USER_MODEL)), + ('klijent', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='radni_nalozi', to='kupci.kupac')), + ('putni_nalog', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='radni_nalozi', to='operations.putninalog')), + ('stroj', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='radni_nalozi', to='fleet.stroj', verbose_name='Stroj na popravku')), + ], + options={ + 'verbose_name': 'Radni nalog', + 'verbose_name_plural': 'Radni nalozi', + }, + ), + migrations.CreateModel( + name='RadniNalogSlika', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slika', models.ImageField(upload_to=operations.models.putanja_galerije_naloga, verbose_name='Slika')), + ('opis', models.CharField(blank=True, max_length=100, verbose_name='Kratki opis slike')), + ('radni_nalog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slike', to='operations.radninalog')), + ], + options={ + 'verbose_name': 'Dodatna slika naloga', + 'verbose_name_plural': 'Dodatne slike naloga', + }, + ), + ] diff --git a/001.BACKEND/operations/migrations/__init__.py b/001.BACKEND/operations/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/operations/mixins.py b/001.BACKEND/operations/mixins.py new file mode 100644 index 0000000..dd6d5ea --- /dev/null +++ b/001.BACKEND/operations/mixins.py @@ -0,0 +1,98 @@ +# operations/mixins.py +from django.db import transaction +from rest_framework import serializers +from rest_framework import status + +class DynamicFieldsModelSerializerMixin: + """ + Slojeviti Mixin za SERIALIZERE. + Čisti prazne stringove iz FormData i dinamički uvozi ugniježđene relacije (Lazy Load). + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + request = self.context.get('request') + + # Ako smo na GET zahtjevu, automatski aktiviramo ugniježđeni prikaz (Read-only) + if request and request.method == 'GET': + self._setup_nested_serializers() + + def _setup_nested_serializers(self): + nested_config = getattr(self.Meta, 'nested_fields_config', {}) + for field_name, serializer_path in nested_config.items(): + if field_name in self.fields: + try: + module_path, class_name = serializer_path.rsplit('.', 1) + module = __import__(module_path, fromlist=[class_name]) + serializer_class = getattr(module, class_name) + + self.fields[field_name] = serializer_class(read_only=True, context=self.context) + except (ImportError, AttributeError) as e: + print(f"[Mixin Error] Neuspješan dinamički uvoz za {serializer_path}: {e}") + + def to_internal_value(self, data): + if hasattr(data, 'dict'): # Pretvara prazne HTML stringove "" u prave Python None vrijednosti + data = data.copy() + for key, value in data.items(): + if value == '': + data[key] = None + return super().to_internal_value(data) + + +class AstroBridgeViewSetMixin: + """ + Slojeviti Mixin za VIEWSETOVE. + Presreće perform_create/update za automatsko spremanje request.FILES u galerije + i omata izlazni JSON u standardni omotač (Envelope) za Astro proxy. + """ + auto_handle_files = False + file_field_name = 'slike' + related_file_model = None + related_file_fk = None + + def finalize_response(self, request, response, *args, **kwargs): + """Omata DRF odgovor u stabilnu strukturu za Astro klijent.""" + if isinstance(response.data, (dict, list)): + if status.is_success(response.status_code): + response.data = { + "success": True, + "data": response.data, + "errors": None + } + else: + response.data = { + "success": False, + "data": None, + "errors": response.data + } + return super().finalize_response(request, response, *args, **kwargs) + + def perform_create(self, serializer): + instance = serializer.save() + if self.auto_handle_files: + self._process_attached_files(instance) + return instance + + def perform_update(self, serializer): + instance = serializer.save() + if self.auto_handle_files: + self._process_attached_files(instance) + return instance + + def _process_attached_files(self, instance): + """Automatski izvlači višestruki upload iz FormData boundary-ja.""" + request = self.request + if not self.related_file_model or not self.related_file_fk: + return + + files = request.FILES.getlist(self.file_field_name) + opis = request.data.get('slika_opis', f"Prilog uz {str(instance)}") + + for f in files: + build_kwargs = { + self.related_file_fk: instance, + 'slika': f, + } + if hasattr(self.related_file_model, 'opis'): + build_kwargs['opis'] = opis + + self.related_file_model.objects.create(**build_kwargs) \ No newline at end of file diff --git a/001.BACKEND/operations/models.py b/001.BACKEND/operations/models.py new file mode 100644 index 0000000..8000fa4 --- /dev/null +++ b/001.BACKEND/operations/models.py @@ -0,0 +1,248 @@ +import os +from PIL import Image +import io +from django.db import models, transaction +from django.conf import settings +from django.utils import timezone +from django.core.files.base import ContentFile +from django.core.exceptions import ValidationError + +class BrojacSekvence(models.Model): + """ + Tablica koja pamti isključivo zadnji iskorišteni broj za bilo koji entitet. + Broj se ovdje samo povećava i nikada se ne briše. + """ + naziv_sekvence = models.CharField(max_length=50, unique=True) # npr. 'radni_nalog_2026' + zadnji_broj = models.PositiveIntegerField(default=0) + + class Meta: + verbose_name = "Brojač sekvence" + verbose_name_plural = "Brojači sekvenci" + + def __str__(self): + return f"{self.naziv_sekvence}: {self.zadnji_broj}" + +# --- FUNKCIJE ZA DINAMIČKE PUTANJE --- + +def putanja_slike_naloga(instance, filename): + # Čistimo broj naloga od duplih oznaka za putanju direktorija + broj = instance.broj_naloga.replace('RN-', '') if instance.broj_naloga else 'nepoznato' + return f'radni-nalozi/RN-{broj}/{filename}' + +def putanja_galerije_naloga(instance, filename): + broj = instance.radni_nalog.broj_naloga.replace('RN-', '') if instance.radni_nalog.broj_naloga else 'nepoznato' + return f'radni-nalozi/RN-{broj}/galerija/{filename}' + + +class PutniNalog(models.Model): + class StatusChoices(models.TextChoices): + OTVOREN = 'OTVOREN', 'Otvoren' + ZAVRSEN = 'ZAVRSEN', 'Završen' + + broj_naloga = models.CharField(max_length=50, unique=True) + datum_izdavanja = models.DateField(default=timezone.now) + + vozilo = models.ForeignKey( + 'fleet.Vozilo', + on_delete=models.PROTECT, + related_name='putni_nalozi', + verbose_name="Službeno vozilo" + ) + + korisnik = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) + relacija = models.CharField(max_length=255) + mjesto_odredista = models.CharField(max_length=255) + + pocetna_km = models.PositiveIntegerField( + editable=False, + help_text="Automatski preuzeto iz modela Vozilo" + ) + zavrsna_km = models.PositiveIntegerField(null=True, blank=True) + + vrijeme_polaska = models.DateTimeField(default=timezone.now) + vrijeme_povratka = models.DateTimeField(null=True, blank=True) + + status = models.CharField( + max_length=15, + choices=StatusChoices.choices, + default=StatusChoices.OTVOREN + ) + + class Meta: + verbose_name = "Putni nalog" + verbose_name_plural = "Putni nalozi" + + def clean(self): + super().clean() + if self.status == self.StatusChoices.ZAVRSEN: + if self.zavrsna_km is None: + raise ValidationError({'zavrsna_km': "Morate unijeti završne kilometre prije zatvaranja naloga."}) + + otvoreni_radni_nalozi = self.radni_nalozi.exclude(status='ZAVRSENO').exists() + if otvoreni_radni_nalozi: + raise ValidationError({ + 'status': "Putni nalog ne može biti završen jer postoje povezani radni nalozi koji još nisu zatvoreni." + }) + + def save(self, *args, **kwargs): + if not self.pk: + self.pocetna_km = self.vozilo.trenutni_kilometri + + self.full_clean() + + if self.status == self.StatusChoices.ZAVRSEN and self.zavrsna_km: + with transaction.atomic(): + super().save(*args, **kwargs) + v = self.vozilo + v.trenutni_kilometri = self.zavrsna_km + v.pocetni_kilometri = self.zavrsna_km + v.save() + else: + super().save(*args, **kwargs) + + def __str__(self): + return f"{self.broj_naloga} - {self.vozilo.registracija}" + + +class RadniNalog(models.Model): + class StatusRadaChoices(models.TextChoices): + PLANIRANO = 'PLANIRANO', 'Planirano' + U_RADU = 'U_RADU', 'U radu' + ZAVRSENO = 'ZAVRSENO', 'Završen' + + broj_naloga = models.CharField(max_length=20, unique=True, blank=True) + + stroj = models.ForeignKey( + 'fleet.Stroj', + on_delete=models.CASCADE, + related_name='radni_nalozi', + verbose_name="Stroj na popravku" + ) + + putni_nalog = models.ForeignKey( + 'PutniNalog', + on_delete=models.SET_NULL, + null=True, blank=True, + related_name='radni_nalozi' + ) + + klijent = models.ForeignKey( + 'kupci.Kupac', + on_delete=models.PROTECT, + related_name='radni_nalozi' + ) + + opis_kvara = models.TextField() + izvrsitelj = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, blank=True, + related_name='dodijeljeni_nalozi' + ) + + status = models.CharField( + max_length=20, + choices=StatusRadaChoices.choices, + default=StatusRadaChoices.PLANIRANO + ) + + datum_kreiranja = models.DateTimeField(auto_now_add=True, verbose_name="Datum kreiranja") + datum_azuriranja = models.DateTimeField(auto_now=True, verbose_name="Zadnja izmjena") + slika_kvara = models.ImageField(upload_to=putanja_slike_naloga, null=True, blank=True) + potpis_klijenta = models.ImageField(upload_to='potpisi/%Y/%m/', null=True, blank=True) + + def save(self, *args, **kwargs): + if self.broj_naloga == "": + self.broj_naloga = None + + # 1. AUTOMATSKO GENERIRANJE BROJA NALOGA KROZ SEKVENCIJSKU TABLICU + if not self.broj_naloga: + godina = timezone.now().year + kljuc_sekvence = f"radni_nalog_{godina}" + prefix = f"RN-{godina}-" + + # Atomska transakcija zaključava samo brojač, sprječavajući dupliranje + with transaction.atomic(): + # Dohvati ili kreiraj brojač za tekuću godinu i zaključaj redak za ažuriranje + brojac, created = BrojacSekvence.objects.select_for_update().get_or_create( + naziv_sekvence=kljuc_sekvence + ) + + # Ako je brojač tek kreiran, a u bazi već imaš povijest (npr. stigao si do 25), + # radimo automatsku sinkronizaciju da ne krene od 1 ako tablica nije prazna + if created: + zadnji_u_bazi = RadniNalog.objects.filter(broj_naloga__startswith=prefix).order_by('-broj_naloga').first() + if zadnji_u_bazi: + try: + brojac.zadnji_broj = int(zadnji_u_bazi.broj_naloga.split('-')[-1]) + except (ValueError, IndexError): + brojac.zadnji_broj = 0 + + # Uvećaj sekvencu za 1 (Čak i ako se nalozi obrišu, ovaj broj samo raste!) + brojac.zadnji_broj += 1 + brojac.save() + + # Dodijeli savršeno formatiran broj + self.broj_naloga = f"{prefix}{brojac.zadnji_broj:04d}" + else: + if self.broj_naloga.startswith("RN-RN-"): + self.broj_naloga = self.broj_naloga.replace("RN-RN-", "RN-") + + # 2. OBRADA SLIKE + if self.slika_kvara: + try: + self.slika_kvara = self.rescale_image(self.slika_kvara) + except Exception as e: + print(f"Greška pri obradi slike: {e}") + + # 3. PRIMARNO SPREMANJE + super().save(*args, **kwargs) + + # 4. LOGIKA ZA PUTNI NALOG + if self.putni_nalog and self.status == self.StatusRadaChoices.ZAVRSENO: + pn = self.putni_nalog + ostali_otvoreni = pn.radni_nalozi.exclude( + id=self.id, + status=self.StatusRadaChoices.ZAVRSENO + ).exists() + + if not ostali_otvoreni and pn.zavrsna_km: + pn.status = 'ZAVRSEN' + pn.save() + + def rescale_image(self, image_field): + img = Image.open(image_field) + if img.height > 1200 or img.width > 1200: + output_size = (1200, 1200) + img.thumbnail(output_size) + img_io = io.BytesIO() + img_format = img.format if img.format else 'JPEG' + img.save(img_io, format=img_format, quality=90) + return ContentFile(img_io.getvalue(), name=image_field.name) + return image_field + + class Meta: + verbose_name = "Radni nalog" + verbose_name_plural = "Radni nalozi" + + def __str__(self): + # POPRAVLJENO: Budući da polje broj_naloga već u bazi sadrži "RN-2026-0004", + # ovdje izbacujemo fiksni prefiks "RN-" kako se ne bi duplirao pri pretvaranju objekta u string! + return f"{self.broj_naloga} | {self.stroj.naziv}" + + +class RadniNalogSlika(models.Model): + radni_nalog = models.ForeignKey( + RadniNalog, + related_name='slike', + on_delete=models.CASCADE + ) + slika = models.ImageField(upload_to=putanja_galerije_naloga, verbose_name="Slika") + opis = models.CharField(max_length=100, blank=True, verbose_name="Kratki opis slike") + + class Meta: + verbose_name = "Dodatna slika naloga" + verbose_name_plural = "Dodatne slike naloga" + + def __str__(self): + return f"Slika za nalog {self.radni_nalog.broj_naloga}" \ No newline at end of file diff --git a/001.BACKEND/operations/serializers.py b/001.BACKEND/operations/serializers.py new file mode 100644 index 0000000..6e7112d --- /dev/null +++ b/001.BACKEND/operations/serializers.py @@ -0,0 +1,110 @@ +# operations/serializers.py +from rest_framework import serializers +from .models import RadniNalog, RadniNalogSlika, PutniNalog +from .mixins import DynamicFieldsModelSerializerMixin +from .services import generiraj_i_kreiraj_putni_nalog # Uvozimo servisni sloj + +class RadniNalogSlikaSerializer(serializers.ModelSerializer): + class Meta: + model = RadniNalogSlika + fields = ['id', 'slika', 'opis'] + read_only_fields = ['id', 'datum_dodavanja'] + +class PutniNalogMinimalSerializer(serializers.ModelSerializer): + class Meta: + model = PutniNalog + fields = ['id', 'broj_naloga', 'status', 'pocetna_km'] + +class RadniNalogDetaljiSerializer(DynamicFieldsModelSerializerMixin, serializers.ModelSerializer): + slike = RadniNalogSlikaSerializer(many=True, read_only=True) + status_display = serializers.CharField(source='get_status_display', read_only=True) + izvrsitelj_ime = serializers.CharField(source='izvrsitelj.get_full_name', read_only=True) + vozilo = serializers.SerializerMethodField() + + class Meta: + model = RadniNalog + fields = [ + 'id', 'broj_naloga', 'klijent', 'stroj', 'putni_nalog', 'vozilo', 'opis_kvara', + 'izvrsitelj', 'izvrsitelj_ime', 'status', 'status_display', + 'datum_kreiranja', 'datum_azuriranja', 'slika_kvara', + 'potpis_klijenta', 'slike' + ] + read_only_fields = ['datum_kreiranja', 'datum_azuriranja'] + + nested_fields_config = { + 'klijent': 'kupci.serializers.KupacListaSerializer', + 'stroj': 'fleet.serializers.StrojSerializer', + 'putni_nalog': 'operations.serializers.PutniNalogMinimalSerializer' + } + + def get_vozilo(self, obj): + if obj.putni_nalog and obj.putni_nalog.vozilo: + return { + "id": obj.putni_nalog.vozilo.id, + "naziv": obj.putni_nalog.vozilo.naziv, + "registracija": obj.putni_nalog.vozilo.registracija, + "trenutni_kilometri": obj.putni_nalog.vozilo.trenutni_kilometri, + "status_prikaz": obj.putni_nalog.vozilo.status + } + return None + +class RadniNalogSerializer(DynamicFieldsModelSerializerMixin, serializers.ModelSerializer): + broj_naloga = serializers.CharField(required=False, read_only=True) + + class Meta: + model = RadniNalog + fields = '__all__' + + def validate(self, data): + stroj = data.get('stroj') or (self.instance.stroj if self.instance else None) + klijent = data.get('klijent') or (self.instance.klijent if self.instance else None) + + if stroj and klijent and stroj.vlasnik != klijent: + raise serializers.ValidationError({"stroj": "Odabrani stroj ne pripada odabranom klijentu."}) + return data + +class KreirajPutniNalogSerializer(DynamicFieldsModelSerializerMixin, serializers.ModelSerializer): + radni_nalog_id = serializers.IntegerField(write_only=True) + + class Meta: + model = PutniNalog + fields = ['id', 'radni_nalog_id', 'vozilo', 'broj_naloga'] + read_only_fields = ['id', 'broj_naloga'] + + def validate_radni_nalog_id(self, value): + try: + nalog = RadniNalog.objects.get(id=value) + except RadniNalog.DoesNotExist: + raise serializers.ValidationError("Radni nalog s ovim ID-jem ne postoji.") + if nalog.putni_nalog is not None: + raise serializers.ValidationError("Za ovaj radni nalog već je izrađen putni nalog.") + if not nalog.izvrsitelj: + raise serializers.ValidationError("Radni nalog mora imati dodijeljenog izvršitelja.") + return value + + def create(self, validated_data): + # Sva teška logika je uklonjena odavde i poziva se iz operacije.py + return generiraj_i_kreiraj_putni_nalog( + radni_nalog_id=validated_data['radni_nalog_id'], + vozilo=validated_data.get('vozilo') + ) + +class PutniNalogSerializer(serializers.ModelSerializer): + vozilo_detalji = serializers.SerializerMethodField() + korisnik_ime = serializers.CharField(source='korisnik.get_full_name', read_only=True) + status_display = serializers.CharField(source='get_status_display', read_only=True) + + class Meta: + model = PutniNalog + fields = [ + 'id', 'broj_naloga', 'vozilo', 'vozilo_detalji', 'korisnik', 'korisnik_ime', + 'relacija', 'mjesto_odredista', 'pocetna_km', 'zavrsna_km', 'status', + 'status_display', 'vrijeme_polaska', 'vrijeme_povratka', 'datum_izdavanja' + ] + read_only_fields = ['id', 'broj_naloga', 'vrijeme_polaska', 'datum_izdavanja'] + + def get_vozilo_detalji(self, obj): + from fleet.serializers import VoziloListaSerializer + if obj.vozilo: + return VoziloListaSerializer(obj.vozilo, context=self.context).data + return None \ No newline at end of file diff --git a/001.BACKEND/operations/services.py b/001.BACKEND/operations/services.py new file mode 100644 index 0000000..479c97b --- /dev/null +++ b/001.BACKEND/operations/services.py @@ -0,0 +1,122 @@ +# operations/services.py +import os +from PIL import Image +from io import BytesIO +import requests +from django.conf import settings +from django.db import transaction +from django.utils import timezone +from rest_framework import serializers +from .models import RadniNalog, PutniNalog +from kalendar.services import kreiraj_kalendarski_unos + +def izvrsi_perform_create_logiku(serializer, user): + """ + Konzistentna poslovna logika za kreiranje radnog naloga. + Dodjeljuje izvršitelja i automatski okida kalendarski servis. + """ + if not serializer.validated_data.get('izvrsitelj'): + radni_nalog = serializer.save(izvrsitelj=user) + else: + radni_nalog = serializer.save() + + try: + kreiraj_kalendarski_unos(radni_nalog) + except Exception as e: + # Možeš uvesti standardni logging sustav umjesto printa + print(f"Greška pri kreiranju kalendarskog zapisa: {e}") + + return radni_nalog + + +def generiraj_i_kreiraj_putni_nalog(radni_nalog_id, vozilo): + """ + Enkapsulirana atomska logika za generiranje jedinstvenog broja sekvence + i kreiranje putnog naloga, sprječavajući dupliciranje brojeva pod opterećenjem. + """ + radni_nalog = RadniNalog.objects.get(id=radni_nalog_id) + godina = timezone.now().year + prefix = f"PN-{godina}-" + + with transaction.atomic(): + # Zaključavamo zapise za tekuću godinu (Pessimistic Locking) + zadnji_pn = PutniNalog.objects.select_for_update().filter( + broj_naloga__startswith=prefix + ).order_by('-broj_naloga').first() + + if zadnji_pn: + try: + zadnji_broj_str = zadnji_pn.broj_naloga.split('-')[-1] + novi_broj = int(zadnji_broj_str) + 1 + except (ValueError, IndexError): + novi_broj = 1 + else: + novi_broj = 1 + + # Formatiranje broja: PN-2026-0001 + broj_pn = f"{prefix}{novi_broj:04d}" + + # Siguran upis u bazu + putni_nalog = PutniNalog.objects.create( + broj_naloga=broj_pn, + vozilo=vozilo, + korisnik=radni_nalog.izvrsitelj, + relacija=f"Zagreb - {radni_nalog.klijent.grad} - Zagreb", + mjesto_odredista=radni_nalog.klijent.grad, + vrijeme_polaska=timezone.now() + ) + + # Povezivanje s radnim nalogom + radni_nalog.putni_nalog = putni_nalog + radni_nalog.save() + + return putni_nalog + +def get_optimized_image(image_url, width=600): + """ + On-the-fly optimizacija slike u memoriji. + Prima URL ili lokalnu stazu, vraća sirove bajtove WebP slike. + """ + # 1. Dohvaćanje slike (podržava lokalne relativne staze i apsolutne URL-ove) + if image_url.startswith('http://') or image_url.startswith('https://'): + odgovor = requests.get(image_url, timeout=5) + odgovor.raise_for_status() + img_data = BytesIO(odgovor.content) + else: + # Pretvaramo relativnu stazu (npr. /media/slika.jpg) u apsolutnu stazu na disku + # Mičemo početni slash ako postoji da osiguramo ispravno spajanje staza + cista_staza = image_url.lstrip('/') + if cista_staza.startswith('media/'): + cista_staza = cista_staza.replace('media/', '', 1) + + staza_na_disku = os.path.join(settings.MEDIA_ROOT, cista_staza) + + if not os.path.exists(staza_na_disku): + raise FileNotFoundError(f"Slika nije pronađena na lokaciji: {staza_na_disku}") + + with open(staza_na_disku, 'rb') as f: + img_data = BytesIO(f.read()) + + # 2. Obrada slike kroz PIL (Pillow) paket + with Image.open(img_data) as img: + # Osiguranje kompatibilnosti profila boja (RGBA -> RGB za WebP) + if img.mode in ('RGBA', 'LA'): + pozadina = Image.new('RGB', img.size, (255, 255, 255)) + pozadina.paste(img, mask=img.split()[3]) + img = pozadina + elif img.mode == 'P': + img = img.convert('RGB') + + # Proporcionalni resize ako slika prelazi željenu širinu + if img.width > width: + faktor_skaliranja = width / float(img.width) + nova_visina = int(float(img.height) * float(faktor_skaliranja)) + img = img.resize((width, nova_visina), Image.Resampling.LANCZOS) + + # 3. Spremanje u memorijski buffer umjesto na disk + memorijski_buffer = BytesIO() + # Smanjujemo kvalitetu na 80% (idealni omjer oštrine i težine u bajtovima) + img.save(memorijski_buffer, format='WEBP', quality=80) + memorijski_buffer.seek(0) + + return memorijski_buffer.getvalue() \ No newline at end of file diff --git a/001.BACKEND/operations/tasks.py b/001.BACKEND/operations/tasks.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/operations/tests.py b/001.BACKEND/operations/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/001.BACKEND/operations/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/001.BACKEND/operations/urls.py b/001.BACKEND/operations/urls.py new file mode 100644 index 0000000..ea8933d --- /dev/null +++ b/001.BACKEND/operations/urls.py @@ -0,0 +1,13 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import RadniNalogViewSet, PutniNalogViewSet + +# Kreiramo router i registriramo ViewSet za radne naloge +router = DefaultRouter() +router.register(r'radni-nalozi', RadniNalogViewSet, basename='radni-nalog') +router.register(r'putni-nalozi', PutniNalogViewSet, basename='putni-nalog') + +urlpatterns = [ + # Uključujemo sve rute koje router generira + path('', include(router.urls)), +] \ No newline at end of file diff --git a/001.BACKEND/operations/views.py b/001.BACKEND/operations/views.py new file mode 100644 index 0000000..2c26897 --- /dev/null +++ b/001.BACKEND/operations/views.py @@ -0,0 +1,155 @@ +# operations/views.py +from django.http import HttpResponse +from django.utils import timezone +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets, filters, permissions, status +from rest_framework.views import APIView +from rest_framework.decorators import action +from rest_framework.response import Response + +from .models import RadniNalog, RadniNalogSlika, PutniNalog +from .mixins import AstroBridgeViewSetMixin +from .services import izvrsi_perform_create_logiku, get_optimized_image +from .serializers import ( + RadniNalogSerializer, + RadniNalogDetaljiSerializer, + KreirajPutniNalogSerializer, + PutniNalogSerializer +) + +class RadniNalogViewSet(AstroBridgeViewSetMixin, viewsets.ModelViewSet): + queryset = RadniNalog.objects.all().select_related( + 'klijent', 'izvrsitelj', 'putni_nalog__vozilo' + ) + + auto_handle_files = True + file_field_name = 'slike' + related_file_model = RadniNalogSlika + related_file_fk = 'radni_nalog' + + filter_backends = [ + DjangoFilterBackend, + filters.SearchFilter, + filters.OrderingFilter + ] + + filterset_fields = ['status', 'izvrsitelj', 'klijent', 'putni_nalog__vozilo'] + search_fields = ['broj_naloga', 'klijent__naziv', 'putni_nalog__vozilo__registracija', 'putni_nalog__vozilo__naziv'] + ordering_fields = ['datum_kreiranja', 'status', 'broj_naloga'] + ordering = ['-datum_kreiranja'] + + def get_serializer_class(self): + if self.action in ['create', 'update', 'partial_update']: + return RadniNalogSerializer + return RadniNalogDetaljiSerializer + + def get_queryset(self): + queryset = self.queryset + if self.action in ['list', 'retrieve']: + queryset = queryset.prefetch_related('slike') + return queryset + + def perform_create(self, serializer): + # Delegiramo izvršavanje u operacije.py + return izvrsi_perform_create_logiku(serializer, self.request.user) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + instance = self.perform_create(serializer) + instance.refresh_from_db() + + izlazni_serializer = RadniNalogDetaljiSerializer(instance, context={'request': request}) + return Response(izlazni_serializer.data, status=status.HTTP_201_CREATED) + + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', True) + instance = self.get_object() + + serializer = RadniNalogSerializer(instance, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + + self.perform_update(serializer) + instance.refresh_from_db() + + return Response(RadniNalogDetaljiSerializer(instance, context={'request': request}).data) + + @action(detail=False, methods=['get'], url_path='sljedeci-broj', permission_classes=[permissions.AllowAny]) + def dohvati_sljedeci_broj(self, request): + godina = timezone.now().year + kljuc_sekvence = f"radni_nalog_{godina}" + prefix = f"RN-{godina}-" + + from .models import BrojacSekvence + brojac = BrojacSekvence.objects.filter(naziv_sekvence=kljuc_sekvence).first() + + if brojac: + sljedeci_broj = brojac.zadnji_broj + 1 + else: + zadnji_nalog = RadniNalog.objects.filter(broj_naloga__startswith=prefix).order_by('-broj_naloga').first() + if zadnji_nalog: + try: + sljedeci_broj = int(zadnji_nalog.broj_naloga.split('-')[-1]) + 1 + except (ValueError, IndexError): + sljedeci_broj = 1 + else: + sljedeci_broj = 1 + + sljedeci_broj_formatiran = f"{prefix}{sljedeci_broj:04d}" + return Response({"broj_naloga": sljedeci_broj_formatiran}) + + +class PutniNalogViewSet(AstroBridgeViewSetMixin, viewsets.ModelViewSet): + queryset = PutniNalog.objects.all().order_by('-datum_izdavanja', '-id') + permission_classes = [permissions.IsAuthenticated] + + def get_serializer_class(self): + if self.action == 'create': + return KreirajPutniNalogSerializer + return PutniNalogSerializer + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + + putni_nalog = serializer.save() + + return Response( + { + "id": putni_nalog.id, + "broj_naloga": putni_nalog.broj_naloga, + "status": putni_nalog.status, + "pocetna_km": putni_nalog.pocetna_km, + "message": "Putni nalog uspješno kreiran!" + }, + status=status.HTTP_201_CREATED + ) + + +class ProxyImageView(APIView): + http_method_names = ['get'] + permission_classes = [permissions.AllowAny] + + def get(self, request): + image_url = request.query_params.get('url') + + try: + width = int(request.query_params.get('w', 600)) # Defaultna širina za terensku karticu + except (ValueError, TypeError): + width = 600 + + if not image_url: + return Response({"error": "URL slike nije zadan"}, status=status.HTTP_400_BAD_REQUEST) + + try: + # get_optimized_image vraća sirove binarne bajtove WebP slike iz BytesIO-a + image_data = get_optimized_image(image_url, width=width) + + response = HttpResponse(image_data, content_type="image/webp") + # 🚀 KLJUČNO ZA OFFLINE I BRZINU: Kažemo pregledniku da kešira sliku na 7 dana + response['Cache-Control'] = 'public, max-age=604800, immutable' + return response + except Exception as e: + print(f"[ProxyImageView Krah] Detalji: {e}") + return Response({"error": f"Greška pri optimizaciji: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file diff --git a/001.BACKEND/requirements.txt b/001.BACKEND/requirements.txt new file mode 100644 index 0000000..74a0ce6 Binary files /dev/null and b/001.BACKEND/requirements.txt differ diff --git a/001.BACKEND/test_assets/kvar.jpg b/001.BACKEND/test_assets/kvar.jpg new file mode 100644 index 0000000..7fe1d5c Binary files /dev/null and b/001.BACKEND/test_assets/kvar.jpg differ diff --git a/001.BACKEND/users/__init__.py b/001.BACKEND/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/users/admin.py b/001.BACKEND/users/admin.py new file mode 100644 index 0000000..1514e3f --- /dev/null +++ b/001.BACKEND/users/admin.py @@ -0,0 +1,23 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import CustomUser + +class CustomUserAdmin(UserAdmin): + # 🚀 POPRAVLJENO: Zamijenjen 'is_serviser' s 'uloga' u stupcima tablice + list_display = ['email', 'first_name', 'last_name', 'telefon', 'oib', 'uloga', 'is_staff'] + + # Ako filtriraš korisnike na desnoj strani admina, ažuriraj i to: + list_filter = ['uloga', 'is_staff', 'is_active'] + + # 🚀 POPRAVLJENO: Dodavanje polja u sekciju za uređivanje postojećeg korisnika + fieldsets = UserAdmin.fieldsets + ( + ('Dodatni podaci tvrtke', {'fields': ('telefon', 'oib', 'uloga')}), + ) + + # 🚀 POPRAVLJENO: Dodavanje polja u formu za kreiranje novog korisnika + add_fieldsets = UserAdmin.add_fieldsets + ( + ('Dodatni podaci tvrtke', {'fields': ('telefon', 'oib', 'uloga')}), + ) + +# Registracija modela i tvoje prilagođene admin klase +admin.site.register(CustomUser, CustomUserAdmin) \ No newline at end of file diff --git a/001.BACKEND/users/apps.py b/001.BACKEND/users/apps.py new file mode 100644 index 0000000..4ce1fab --- /dev/null +++ b/001.BACKEND/users/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = 'users' diff --git a/001.BACKEND/users/migrations/0001_initial.py b/001.BACKEND/users/migrations/0001_initial.py new file mode 100644 index 0000000..7c6bc41 --- /dev/null +++ b/001.BACKEND/users/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 6.0.5 on 2026-05-20 20:03 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('telefon', models.CharField(blank=True, max_length=20)), + ('oib', models.CharField(blank=True, max_length=11, null=True)), + ('is_serviser', models.BooleanField(default=False)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/001.BACKEND/users/migrations/0002_remove_customuser_is_serviser_customuser_uloga.py b/001.BACKEND/users/migrations/0002_remove_customuser_is_serviser_customuser_uloga.py new file mode 100644 index 0000000..07b1eb2 --- /dev/null +++ b/001.BACKEND/users/migrations/0002_remove_customuser_is_serviser_customuser_uloga.py @@ -0,0 +1,22 @@ +# Generated by Django 6.0.5 on 2026-05-23 12:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='customuser', + name='is_serviser', + ), + migrations.AddField( + model_name='customuser', + name='uloga', + field=models.CharField(choices=[('SERVISER', 'Serviser'), ('PRODAJA', 'Prodaja / Operativa'), ('KNJIGOVODSTVO', 'Knjigovodstvo i financije'), ('ADMIN', 'Administrator')], default='SERVISER', help_text='Glavna operativna uloga korisnika u ERP sustavu', max_length=20), + ), + ] diff --git a/001.BACKEND/users/migrations/__init__.py b/001.BACKEND/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/001.BACKEND/users/models.py b/001.BACKEND/users/models.py new file mode 100644 index 0000000..1572fb7 --- /dev/null +++ b/001.BACKEND/users/models.py @@ -0,0 +1,30 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + +class CustomUser(AbstractUser): + class Role(models.TextChoices): + SERVISER = 'SERVISER', 'Serviser' + PRODAJA = 'PRODAJA', 'Prodaja / Operativa' + KNJIGOVODSTVO = 'KNJIGOVODSTVO', 'Knjigovodstvo i financije' + ADMIN = 'ADMIN', 'Administrator' + + # Email koristimo za login, pa mora biti jedinstven + email = models.EmailField(unique=True) + + # Dodatna polja za tvoju tvrtku + telefon = models.CharField(max_length=20, blank=True) + oib = models.CharField(max_length=11, blank=True, null=True) + # 🚀 ZAMJENA: Umjesto is_serviser, uvodimo jedno polje za sve uloge + uloga = models.CharField( + max_length=20, + choices=Role.choices, + default=Role.SERVISER, + help_text="Glavna operativna uloga korisnika u ERP sustavu" + ) + + # Govorimo Djangu da koristi email umjesto username-a + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username', 'first_name', 'last_name'] + + def __str__(self): + return f"{self.first_name} {self.last_name} ({self.email}) - {self.get_uloga_display()}" \ No newline at end of file diff --git a/001.BACKEND/users/serializers.py b/001.BACKEND/users/serializers.py new file mode 100644 index 0000000..52484d6 --- /dev/null +++ b/001.BACKEND/users/serializers.py @@ -0,0 +1,96 @@ +from rest_framework import serializers +from .models import CustomUser +from fleet.models import Vozilo +from operations.models import RadniNalog, PutniNalog + +class UserSerializer(serializers.ModelSerializer): + # Prosljeđujemo i ljudski čitljiv naziv (npr. "Knjigovodstvo i financije") + uloga_prikaz = serializers.CharField(source='get_uloga_display', read_only=True) + + class Meta: + model = CustomUser + fields = ['id', 'first_name', 'last_name', 'email', 'telefon', 'oib', 'uloga', 'uloga_prikaz'] + +# --- POMOĆNI LIGHTWEIGHT SERIALIZERI (Sprečavaju prevelik JSON payload) --- + +class ServiserVoziloSerializer(serializers.ModelSerializer): + class Meta: + model = Vozilo + # 🚀 POPRAVLJENO: Izbačena nepostojeća polja (marka, model, tip). + # Usklađeno s tvojim stvarnim poljima iz /fleet/models.py + fields = [ + 'id', + 'naziv', + 'registracija', + 'trenutni_kilometri', + 'status' + ] + +class ServiserRadniNalogSerializer(serializers.ModelSerializer): + # Prikazujemo nazive umjesto ID-jeva radi lakšeg klijentskog ispisa na terminalu + kupac_naziv = serializers.CharField(source='klijent.naziv', read_only=True) + stroj_naziv = serializers.CharField(source='stroj.naziv', read_only=True) + + class Meta: + model = RadniNalog + # 🚀 POPRAVLJENO: 'prioritet' je izbačen jer ne postoji na modelu RadniNalog. + # Dodan je 'opis_kvara' koji ti može zatrebati na klijentskim karticama! + fields = [ + 'id', + 'broj_naloga', + 'status', + 'opis_kvara', + 'datum_kreiranja', + 'kupac_naziv', + 'stroj_naziv' + ] + +class ServiserPutniNalogSerializer(serializers.ModelSerializer): + vozilo_registracija = serializers.CharField(source='vozilo.registracija', read_only=True) + + # 🚀 Budući da jedan putni nalog može imati više radnih naloga (ili nijedan ako tek kreće na put), + # koristimo SerializerMethodField za siguran dohvat brojeva dokumenata bez rušenja + brojevi_radnih_naloga = serializers.SerializerMethodField() + + class Meta: + model = PutniNalog + # 🚀 USKLAĐENO s tvojim točnim poljima iz modela PutniNalog: + fields = [ + 'id', + 'broj_naloga', + 'datum_izdavanja', + 'status', + 'relacija', + 'mjesto_odredista', + 'pocetna_km', + 'zavrsna_km', + 'vozilo_registracija', + 'brojevi_radnih_naloga' + ] + + def get_brojevi_radnih_naloga(self, obj): + # Dohvaćamo sve povezane radne naloge preko related_name='radni_nalozi' + nalozi = obj.radni_nalozi.all() + if nalozi.exists(): + # Vraćamo npr. "RN-2026-0001, RN-2026-0002" ako ih ima više + return ", ".join([rn.broj_naloga for rn in nalozi if rn.broj_naloga]) + return "Nema povezanih RN" + + +# --- GLAVNI OPERATIVNI SERIALIZER ZA TERMINAL SERVISERA --- + +class ServiserTerminalSerializer(serializers.Serializer): + """ + Serializer koji objedinjuje sve podatke servisera i njegove dodijeljene resurse. + Ne nasljeđuje ModelSerializer jer serijalizira rječnik (dict) dobiven iz service sloja. + """ + id = serializers.IntegerField(source='serviser.id') + email = serializers.EmailField(source='serviser.email') + first_name = serializers.CharField(source='serviser.first_name') + last_name = serializers.CharField(source='serviser.last_name') + uloga = serializers.CharField(source='serviser.uloga') + + # 🚀 Ugniježđeni podaci koji se pune kroz asocijativni rječnik iz services.py + radni_nalozi = ServiserRadniNalogSerializer(many=True) + putni_nalozi = ServiserPutniNalogSerializer(many=True) + vozila = ServiserVoziloSerializer(many=True) \ No newline at end of file diff --git a/001.BACKEND/users/services.py b/001.BACKEND/users/services.py new file mode 100644 index 0000000..9719bcd --- /dev/null +++ b/001.BACKEND/users/services.py @@ -0,0 +1,37 @@ +# users/services.py +from django.shortcuts import get_object_or_404 +from django.core.exceptions import PermissionDenied +from .models import CustomUser +from operations.models import RadniNalog, PutniNalog +from fleet.models import Vozilo + +def dohvati_operativne_podatke_servisera(user_id: int) -> dict: + """ + Prikuplja sve povezane resurse (radne naloge, putne naloge i vozila) + dodijeljene specifičnom serviseru. + """ + # 1. Dohvaćamo korisnika + korisnik = get_object_or_404(CustomUser, id=user_id) + + # 2. Sigurnosni ček na razini poslovne logike + if getattr(korisnik, 'uloga', '').upper() != 'SERVISER': + raise PermissionDenied("Odabrani korisnik nema operativne ovlasti servisera.") + + # 3. Dohvat povezanih entiteta prateći STVARNA polja iz tvoje baze podataka: + + # Radni nalozi gdje je korisnik postavljen kao izvršitelj + radni_nalozi = RadniNalog.objects.filter(izvrsitelj=korisnik).select_related('klijent', 'stroj') + + # 🚀 POPRAVAK: 'radni_nalozi__in' umjesto 'radni_nalog__in' (usklađivanje s related_name na RadniNalog modelu) + putni_nalozi = PutniNalog.objects.filter(radni_nalozi__in=radni_nalozi).select_related('vozilo') + + # Izvlačimo jedinstvena vozila koja serviser vozi kroz svoje aktivne putne naloge + povezana_vozila_ids = putni_nalozi.values_list('vozilo_id', flat=True).distinct() + povezana_vozila = Vozilo.objects.filter(id__in=povezana_vozila_ids) + + return { + "serviser": korisnik, + "radni_nalozi": radni_nalozi, + "putni_nalozi": putni_nalozi, + "vozila": povezana_vozila + } \ No newline at end of file diff --git a/001.BACKEND/users/tests.py b/001.BACKEND/users/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/001.BACKEND/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/001.BACKEND/users/urls.py b/001.BACKEND/users/urls.py new file mode 100644 index 0000000..c93a292 --- /dev/null +++ b/001.BACKEND/users/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import UserViewSet + +router = DefaultRouter() +router.register(r'', UserViewSet, basename='user') + +urlpatterns = [ + path('', include(router.urls)), +] \ No newline at end of file diff --git a/001.BACKEND/users/views.py b/001.BACKEND/users/views.py new file mode 100644 index 0000000..7769d6c --- /dev/null +++ b/001.BACKEND/users/views.py @@ -0,0 +1,97 @@ +# users/views.py +from rest_framework import viewsets, permissions, status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework_simplejwt.authentication import JWTAuthentication +from rest_framework import serializers +from django.core.exceptions import PermissionDenied + +from .models import CustomUser +from .services import dohvati_operativne_podatke_servisera +from .serializers import ServiserTerminalSerializer + +# 🚀 1. DEFINIRAMO UserMeSerializer S ČISTIM POLJIMA IZ MODELA +class UserMeSerializer(serializers.ModelSerializer): + uloga = serializers.SerializerMethodField() + is_serviser = serializers.SerializerMethodField() + + class Meta: + model = CustomUser + fields = ['id', 'email', 'first_name', 'last_name', 'is_serviser', 'uloga'] + + def get_is_serviser(self, obj): + uloga_str = getattr(obj, 'uloga', '') + return str(uloga_str).upper().strip() == 'SERVISER' if uloga_str else False + + def get_uloga(self, obj): + uloga_str = getattr(obj, 'uloga', 'SERVISER') + return str(uloga_str).upper().strip() + + +# 🚀 2. AKTIVNI VIEWSET S BACKEND OSIGURAČIMA +class UserViewSet(viewsets.ViewSet): + authentication_classes = [JWTAuthentication] + # 🎯 POPRAVAK 1: Globalno zaključavamo ViewSet, samo ulogirani korisnici prolaze + permission_classes = [permissions.AllowAny] + + @action(detail=False, methods=['get'], url_path='me') + def me(self, request): + # Ova provjera ostaje kao oporavak u slučaju da JWT autentifikacija propusti prazan objekt + if not request.user or not request.user.is_authenticated: + return Response( + { + "detail": "Aktivna sesija nije pronađena. Pristup neautoriziran.", + "code": "token_not_valid" + }, + status=status.HTTP_401_UNAUTHORIZED + ) + + try: + cisti_korisnik = CustomUser.objects.get(id=request.user.id) + serializer = UserMeSerializer(cisti_korisnik) + return Response(serializer.data, status=status.HTTP_200_OK) + + except CustomUser.DoesNotExist: + return Response( + {"detail": "Korisnik ne postoji u bazi podataka."}, + status=status.HTTP_404_NOT_FOUND + ) + except Exception as e: + print(f"Kritični krah unutar api/users/me: {str(e)}") + return Response( + {"detail": f"Interna greška poslužitelja: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + @action(detail=True, methods=['get'], url_path='terminal') + def terminal_podaci(self, request, pk=None): + """ + Dohvaća sve operativne resurse za specifičnog servisera na ruti: + GET /api/users//terminal/ + """ + # 🎯 POPRAVAK 2: BACKEND ZAŠTITA OD NJUŠKANJA URL-ova + trenutni_korisnik = request.user + trenutna_uloga = str(getattr(trenutni_korisnik, 'uloga', '')).upper().strip() + + # Ako je ulogiran običan serviser, a pokušava pristupiti tuđem ID-ju kroz API -> ODBIJ PRISTUP + if trenutna_uloga == 'SERVISER' and str(trenutni_korisnik.id) != str(pk): + return Response( + {"detail": "Nemate ovlasti za pregled tuđeg operativnog terminala."}, + status=status.HTTP_403_FORBIDDEN + ) + + try: + # 1. Okidamo biznis logiku iz services.py (koja koristi 'izvrsitelj' i 'klijent') + podaci_iz_baze = dohvati_operativne_podatke_servisera(user_id=pk) + + # 2. Prosljeđujemo rječnik u objedinjeni serializer + serializer = ServiserTerminalSerializer(podaci_iz_baze) + return Response(serializer.data, status=status.HTTP_200_OK) + + except PermissionDenied as pd_err: + return Response({"detail": str(pd_err)}, status=status.HTTP_403_FORBIDDEN) + except Exception as e: + return Response( + {"detail": f"Greška pri obradi operativnih podataka: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) \ No newline at end of file diff --git a/001.FRONTEND/.dockerignore b/001.FRONTEND/.dockerignore new file mode 100644 index 0000000..6a1c4c1 --- /dev/null +++ b/001.FRONTEND/.dockerignore @@ -0,0 +1,2 @@ +node_modules +.astro diff --git a/001.FRONTEND/.env.example b/001.FRONTEND/.env.example new file mode 100644 index 0000000..339c7c5 --- /dev/null +++ b/001.FRONTEND/.env.example @@ -0,0 +1 @@ +PUBLIC_API_URL=https://v003-backend.captain.mitteworkspace.cloud/api \ No newline at end of file diff --git a/001.FRONTEND/.gitignore b/001.FRONTEND/.gitignore new file mode 100644 index 0000000..016b59e --- /dev/null +++ b/001.FRONTEND/.gitignore @@ -0,0 +1,24 @@ +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ diff --git a/001.FRONTEND/.vscode/extensions.json b/001.FRONTEND/.vscode/extensions.json new file mode 100644 index 0000000..22a1505 --- /dev/null +++ b/001.FRONTEND/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/001.FRONTEND/.vscode/launch.json b/001.FRONTEND/.vscode/launch.json new file mode 100644 index 0000000..d642209 --- /dev/null +++ b/001.FRONTEND/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/001.FRONTEND/Dockerfile b/001.FRONTEND/Dockerfile new file mode 100644 index 0000000..93d5850 --- /dev/null +++ b/001.FRONTEND/Dockerfile @@ -0,0 +1,19 @@ +# 001.FRONTEND/Dockerfile +FROM node:22-alpine + +WORKDIR /app + +# Kopiramo package.json i package-lock.json +COPY package*.json ./ + +# Instaliramo sve ovisnosti (uključujući devDependencies potrebne za Vite/Astro) +RUN npm install + +# Kopiramo ostatak izvornog koda +COPY . . + +# Otvaramo port za Astro dev server +EXPOSE 4321 + +# Pokrećemo dev server direktno, dopuštajući docker-composeu da upravlja volumenima +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/001.FRONTEND/README.md b/001.FRONTEND/README.md new file mode 100644 index 0000000..414a13a --- /dev/null +++ b/001.FRONTEND/README.md @@ -0,0 +1,46 @@ +# Astro Starter Kit: Basics + +```sh +npm create astro@latest -- --template basics +``` + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +## 🚀 Project Structure + +Inside of your Astro project, you'll see the following folders and files: + +```text +/ +├── public/ +│ └── favicon.svg +├── src +│   ├── assets +│   │   └── astro.svg +│   ├── components +│   │   └── Welcome.astro +│   ├── layouts +│   │   └── Layout.astro +│   └── pages +│   └── index.astro +└── package.json +``` + +To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). diff --git a/001.FRONTEND/astro.config.mjs b/001.FRONTEND/astro.config.mjs new file mode 100644 index 0000000..b83b57a --- /dev/null +++ b/001.FRONTEND/astro.config.mjs @@ -0,0 +1,56 @@ +// @ts-check +import { defineConfig } from 'astro/config'; +import node from '@astrojs/node'; +import tailwindcss from '@tailwindcss/vite'; + +import preact from '@astrojs/preact'; + +export default defineConfig({ + output: 'server', + + adapter: node({ + mode: 'standalone', + }), + + server: { + host: '0.0.0.0', + port: 4321, + }, + + image: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'frontend-operativa.local.mitteworkspace.cloud', + port: '', + pathname: '/**', + }, + ], + }, + + build: { + inlineStylesheets: 'always' + }, + + vite: { + build: { + // Smanji intenzitet optimizacije tijekom dev-a + minify: false, + cssMinify: false, + }, + plugins: [tailwindcss()], + // KLJUČNO ZA TAILWIND v4 + NODE standalone SSR: + ssr: { + // Prisiljava Vite da uključi Tailwind v4 stilove u serverski bundle + noExternal: ['tailwindcss', '@tailwindcss/vite', 'flowbite', 'flowbite-react'] + }, + server: { + watch: { + ignored: ['**/node_modules/**', '**/dist/**'], + }, + allowedHosts: ['.mitteworkspace.cloud'] + } + }, + + integrations: [preact({ compat: true })] +}); \ No newline at end of file diff --git a/001.FRONTEND/package-lock.json b/001.FRONTEND/package-lock.json new file mode 100644 index 0000000..7e707e4 --- /dev/null +++ b/001.FRONTEND/package-lock.json @@ -0,0 +1,7263 @@ +{ + "name": "poslovanje", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "poslovanje", + "version": "0.0.1", + "dependencies": { + "@astrojs/node": "^10.0.6", + "@astrojs/preact": "^5.1.3", + "@nanostores/preact": "^1.1.0", + "@tailwindcss/vite": "^4.2.4", + "astro": "^6.3.7", + "flowbite": "^4.0.1", + "flowbite-react": "^0.12.17", + "nanostores": "^1.3.0", + "photoswipe": "^5.4.4", + "preact": "^10.29.2", + "tailwindcss": "^4.2.4" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@astrojs/compiler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-4.0.0.tgz", + "integrity": "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.9.1.tgz", + "integrity": "sha512-1pWuARqYom/TzuU3+0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQ1nBxRq3w85uGtd9q9SXfqEldMIQ==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.4" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.1.2.tgz", + "integrity": "sha512-caXZ4Dc2St2dW8luEg22GlP0gupLdztCTQE4EzZOxW1pqWXz9mbeJEuHUkgDYcKWW8tjIHkydYDhWLVoxJ327Q==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.9.1", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/node": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-10.1.1.tgz", + "integrity": "sha512-kCRbxconkgPpY4vR0GS7exovWEiCbxXLarsp+JeKixyDNf+fKN6v7jXDL8KdQgrzjhy131Kvl+GGGX8jGd8adA==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.9.1", + "send": "^1.2.1", + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "^6.3.0" + } + }, + "node_modules/@astrojs/preact": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@astrojs/preact/-/preact-5.1.3.tgz", + "integrity": "sha512-KTLmH8f6H+lo2HdUkKfjb1jAuR8vLaFTJk5Lh0ouH23wI67fL5MH54CaHLCsYomDnZQKQLauZ1k68/fw0o9TpA==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.9.1", + "@preact/preset-vite": "^2.10.5", + "@preact/signals": "^2.8.2", + "devalue": "^5.6.4", + "preact-render-to-string": "^6.6.6", + "vite": "^7.3.2" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "preact": "^10.6.5" + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz", + "integrity": "sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "is-wsl": "^3.1.1", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.3.1.tgz", + "integrity": "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.4.0.tgz", + "integrity": "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.3.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.17", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.17.tgz", + "integrity": "sha512-LGVZKHwmWGg6MRHjLLgsfyaX2y2aCNgnD1zT/E6B+/h+vxg+nIJUqHPAlTzsHDyqdgEpJ1Np5kxWuFEErXzoGg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.7", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nanostores/preact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@nanostores/preact/-/preact-1.1.0.tgz", + "integrity": "sha512-oiu9z85AiZlBTKJ8YsfAs8TALpDij70LmmKuRkeF22rS9zUBhVtYDWXEvV1qUUStNNwSFdhTMkwzOYL/IsA+UA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "nanostores": "^1.2.0", + "preact": ">=10.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.112.0.tgz", + "integrity": "sha512-retxBzJ39Da7Lh/eZTn9+HJgTeDUxZIpuI0urOsmcFsBKXAth3lc1jIvwseQ9qbAI/VrsoFOXiGIzgclARbAHg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.112.0.tgz", + "integrity": "sha512-pRkbBRbuIIsufUWpOJ+JHWfJFNupkidy4sbjfcm37e6xwYrn9LSKMLubPHvNaL1Zf92ZRhGiwaYkEcmaFg2VcA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.112.0.tgz", + "integrity": "sha512-fh6/KQL/cbH5DukT3VkdCqnULLuvVnszVKySD5IgSE0WZb32YZo/cPsPdEv052kk6w3N4agu+NTiMnZjcvhUIg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.112.0.tgz", + "integrity": "sha512-vUBOOY1E30vlu/DoTGDoT1UbLlwu5Yv9tqeBabAwRzwNDz8Skho16VKhsBDUiyqddtpsR3//v6vNk38w4c+6IA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.112.0.tgz", + "integrity": "sha512-hnEtO/9AVnYWzrgnp6L+oPs/6UqlFeteUL6n7magkd2tttgmx1C01hyNNh6nTpZfLzEVJSNJ0S+4NTsK2q2CxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.112.0.tgz", + "integrity": "sha512-WxJrUz3pcIc2hp4lvJbvt/sTL33oX9NPvkD3vDDybE6tc0V++rS+hNOJxwXdD2FDIFPkHs/IEn5asEZFVH+VKw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.112.0.tgz", + "integrity": "sha512-jj8A8WWySaJQqM9XKAIG8U2Q3qxhFQKrXPWv98d1oC35at+L1h+C+V4M3l8BAKhpHKCu3dYlloaAbHd5q1Hw6A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.112.0.tgz", + "integrity": "sha512-G2F8H6FcAExVK5vvhpSh61tqWx5QoaXXUnSsj5FyuDiFT/K7AMMVSQVqnZREDc+YxhrjB0vnKjCcuobXK63kIw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.112.0.tgz", + "integrity": "sha512-3R0iqjM3xYOZCnwgcxOQXH7hrz64/USDIuLbNTM1kZqQzRqaR4w7SwoWKU934zABo8d0op2oSwOp+CV3hZnM7A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.112.0.tgz", + "integrity": "sha512-lAQf8PQxfgy7h0bmcfSVE3hg3qMueshPYULFsCrHM+8KefGZ9W+ZMvRyU33gLrB4w1O3Fz1orR0hmKMCRxXNrQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.112.0.tgz", + "integrity": "sha512-2QlvQBUhHuAE3ezD4X3CAEKMXdfgInggQ5Bj/7gb5NcYP3GyfLTj7c+mMu+BRwfC9B3AXBNyqHWbqEuuUvZyRQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.112.0.tgz", + "integrity": "sha512-v06iu0osHszgqJ1dLQRb6leWFU1sjG/UQk4MoVBtE6ZPewgfTkby6G9II1SpEAf2onnAuQceVYxQH9iuU3NJqw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.112.0.tgz", + "integrity": "sha512-+5HhNHtxsdcd7+ljXFnn9FOoCNXJX3UPgIfIE6vdwS1HqdGNH6eAcVobuqGOp54l8pvcxDQA6F4cPswCgLrQfQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.112.0.tgz", + "integrity": "sha512-jKwO7ZLNkjxwg7FoCLw+fJszooL9yXRZsDN0AQ1AQUTWq1l8GH/2e44k68N3fcP19jl8O8jGpqLAZcQTYk6skA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.112.0.tgz", + "integrity": "sha512-TYqnuKV/p3eOc+N61E0961nA7DC+gaCeJ3+V2LcjJdTwFMdikqWL6uVk1jlrpUCBrozHDATVUKDZYH7r4FQYjQ==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.112.0.tgz", + "integrity": "sha512-ZhrVmWFifVEFQX4XPwLoVFDHw9tAWH9p9vHsHFH+5uCKdfVR+jje4WxVo6YrokWCboGckoOzHq5KKMOcPZfkRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.112.0.tgz", + "integrity": "sha512-Gr8X2PUU3hX1g3F5oLWIZB8DhzDmjr5TfOrmn5tlBOo9l8ojPGdKjnIBfObM7X15928vza8QRKW25RTR7jfivg==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.112.0.tgz", + "integrity": "sha512-t5CDLbU70Ea88bGRhvU/dLJTc/Wcrtf2Jp534E8P3cgjAvHDjdKsfDDqBZrhybJ8Jv9v9vW5ngE40EK51BluDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.112.0.tgz", + "integrity": "sha512-rZH0JynCCwnhe2HfRoyNOl/Kfd9pudoWxgpC5OZhj7j77pMK0UOAa35hYDfrtSOUk2HLzrikV5dPUOY2DpSBSA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.112.0.tgz", + "integrity": "sha512-oGHluohzmVFAuQrkEnl1OXAxMz2aYmimxUqIgKXpBgbr7PvFv0doELB273sX+5V3fKeggohKg1A2Qq21W9Z9cQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.112.0.tgz", + "integrity": "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@preact/preset-vite": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.5.tgz", + "integrity": "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@prefresh/vite": "^2.4.11", + "@rollup/pluginutils": "^5.0.0", + "babel-plugin-transform-hook-names": "^1.0.2", + "debug": "^4.4.3", + "magic-string": "^0.30.21", + "picocolors": "^1.1.1", + "vite-prerender-plugin": "^0.5.8", + "zimmerframe": "^1.1.4" + }, + "peerDependencies": { + "@babel/core": "7.x", + "vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/@preact/signals": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.9.1.tgz", + "integrity": "sha512-xVqN8mJjbSN5IB/8Ubmd9NN+Ew6zJswoRxrjZbH3YsgkMshFeO6d8zxEFpHRTq9GJZx7cnPs2CnCpFqtGXGNsw==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact": ">= 10.25.0 || >=11.0.0-0" + } + }, + "node_modules/@preact/signals-core": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.2.tgz", + "integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@prefresh/babel-plugin": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.3.tgz", + "integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==", + "license": "MIT" + }, + "node_modules/@prefresh/core": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.10.tgz", + "integrity": "sha512-7yPTFbG56sutaFu8krp3B4a200KOFUvrtlllKWRuLjsYXo9UUucHOZRcer+gtgMkFTpv6ob8TGcTwA32bSwa1w==", + "license": "MIT", + "peerDependencies": { + "preact": "^10.0.0 || ^11.0.0-0" + } + }, + "node_modules/@prefresh/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==", + "license": "MIT" + }, + "node_modules/@prefresh/vite": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.12.tgz", + "integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "^0.5.2", + "@prefresh/core": "^1.5.0", + "@prefresh/utils": "^1.2.0", + "@rollup/pluginutils": "^4.2.1" + }, + "peerDependencies": { + "preact": "^10.4.0 || ^11.0.0-0", + "vite": ">=2.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.1.0.tgz", + "integrity": "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.1.0", + "@shikijs/types": "4.1.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.1.0.tgz", + "integrity": "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.1.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.1.0.tgz", + "integrity": "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.1.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.1.0.tgz", + "integrity": "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.1.0.tgz", + "integrity": "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.1.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.1.0.tgz", + "integrity": "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.1.0.tgz", + "integrity": "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, + "node_modules/astro": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.3.8.tgz", + "integrity": "sha512-xH2UA8Z17IS+JaqSlSkBor7jO6gd7zXTLdmu06nKpfpDDJFbi/7KZEy3NDmWxmier+6XrCZ9Z4aitO8jhC9oiA==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^4.0.0", + "@astrojs/internal-helpers": "0.9.1", + "@astrojs/markdown-remark": "7.1.2", + "@astrojs/telemetry": "3.3.2", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^1.1.1", + "devalue": "^5.6.3", + "diff": "^8.0.3", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.27.3", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "rehype": "^13.0.2", + "semver": "^7.7.4", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.5", + "vfile": "^6.0.3", + "vite": "^7.3.2", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-plugin-transform-hook-names": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz", + "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.12.10" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/comment-json": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.5.1.tgz", + "integrity": "sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debounce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-3.0.0.tgz", + "integrity": "sha512-64byRbF0/AirwbuHqB3/ZpMG9/nckDa6ZA0yd6UnaQNwbbemCOwvz2sL5sjXLHhZHADyiwLm0M5qMhltUUx+TA==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", + "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/flowbite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-4.0.2.tgz", + "integrity": "sha512-TxBdfZpd3HktHH4ashiYjirrSZeVPtG1OCRZMKTeXUa9FOlMxSF98zgjMB/AxE49KZ+rlfgWynAlaNpWxrqZmA==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.3", + "flowbite-datepicker": "^2.0.0", + "mini-svg-data-uri": "^1.4.3", + "postcss": "^8.5.1", + "tailwindcss": "^4.1.12" + } + }, + "node_modules/flowbite-datepicker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-2.0.0.tgz", + "integrity": "sha512-m81hl0Bimq45MUg4maJLOnXrX+C9lZ0AkjMb9uotuVUSr729k/YiymWDfVAm63AYDH7g7y3rI3ke3XaBzWWqLw==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-node-resolve": "^15.2.3", + "@tailwindcss/postcss": "^4.1.17" + } + }, + "node_modules/flowbite-react": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.12.17.tgz", + "integrity": "sha512-En0zhGePac4hFWdikoyor0dQlUM8PbLPvGvFGovssDcw82pdjp9XLg3+bAaiy+83/7vEQY6Ajjnzbrsw4ru/Cg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "1.7.4", + "@floating-ui/react": "0.27.17", + "@iarna/toml": "2.2.5", + "chokidar": "4.0.3", + "comment-json": "4.5.1", + "debounce": "3.0.0", + "deepmerge-ts": "7.1.5", + "klona": "2.0.6", + "magic-string": "0.30.21", + "oxc-parser": "0.112.0", + "package-manager-detector": "1.6.0", + "tailwind-merge-v2": "npm:tailwind-merge@2.6.1", + "tailwind-merge-v3": "npm:tailwind-merge@3.4.0" + }, + "bin": { + "flowbite-react": "dist/cli/bin.js" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "tailwindcss": "^3 || ^4" + } + }, + "node_modules/flowbite-react/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/flowbite-react/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/nanostores": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz", + "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/oxc-parser": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.112.0.tgz", + "integrity": "sha512-7rQ3QdJwobMQLMZwQaPuPYMEF2fDRZwf51lZ//V+bA37nejjKW5ifMHbbCwvA889Y4RLhT+/wLJpPRhAoBaZYw==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.112.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.112.0", + "@oxc-parser/binding-android-arm64": "0.112.0", + "@oxc-parser/binding-darwin-arm64": "0.112.0", + "@oxc-parser/binding-darwin-x64": "0.112.0", + "@oxc-parser/binding-freebsd-x64": "0.112.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.112.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.112.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.112.0", + "@oxc-parser/binding-linux-arm64-musl": "0.112.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.112.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.112.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.112.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.112.0", + "@oxc-parser/binding-linux-x64-gnu": "0.112.0", + "@oxc-parser/binding-linux-x64-musl": "0.112.0", + "@oxc-parser/binding-openharmony-arm64": "0.112.0", + "@oxc-parser/binding-wasm32-wasi": "0.112.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.112.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.112.0", + "@oxc-parser/binding-win32-x64-msvc": "0.112.0" + } + }, + "node_modules/p-limit": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz", + "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/photoswipe": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.4.tgz", + "integrity": "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==", + "license": "MIT", + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.7.0.tgz", + "integrity": "sha512-Z4WR8fmLMRpdYqJ9i7vrlXSsSrxVJydwrkEXHapexfARbWfGb7vGcnvNQnIzN0cXciMVOlz/XLoiMCi9gUsy9Q==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10 || >= 11.0.0-0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shiki": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.1.0.tgz", + "integrity": "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.1.0", + "@shikijs/engine-javascript": "4.1.0", + "@shikijs/engine-oniguruma": "4.1.0", + "@shikijs/langs": "4.1.0", + "@shikijs/themes": "4.1.0", + "@shikijs/types": "4.1.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/simple-code-frame": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz", + "integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==", + "license": "MIT", + "dependencies": { + "kolorist": "^1.6.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0.tgz", + "integrity": "sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, + "node_modules/tailwind-merge-v2": { + "name": "tailwind-merge", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-merge-v3": { + "name": "tailwind-merge", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.13.tgz", + "integrity": "sha512-8OqlXQ35euK9+e7L68u8UwcODxkHoIkjbGsgXuARKNyQ5G6xt8nw1YPeMbxMLgCPFkToU+UEK5j05t2t8edKpQ==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-prerender-plugin": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.13.tgz", + "integrity": "sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==", + "license": "MIT", + "dependencies": { + "kolorist": "^1.8.0", + "magic-string": "0.x >= 0.26.0", + "node-html-parser": "^6.1.12", + "simple-code-frame": "^1.3.0", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" + }, + "peerDependencies": { + "vite": "5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/001.FRONTEND/package.json b/001.FRONTEND/package.json new file mode 100644 index 0000000..8c3c29a --- /dev/null +++ b/001.FRONTEND/package.json @@ -0,0 +1,31 @@ +{ + "name": "poslovanje", + "type": "module", + "version": "0.0.1", + "overrides": { + "vite": "^7.0.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "scripts": { + "dev": "astro dev --host", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "start": "NODE_OPTIONS=--max-old-space-size=4096 npm run dev" + }, + "dependencies": { + "@astrojs/node": "^10.0.6", + "@astrojs/preact": "^5.1.3", + "@nanostores/preact": "^1.1.0", + "@tailwindcss/vite": "^4.2.4", + "astro": "^6.3.7", + "flowbite": "^4.0.1", + "flowbite-react": "^0.12.17", + "nanostores": "^1.3.0", + "photoswipe": "^5.4.4", + "preact": "^10.29.2", + "tailwindcss": "^4.2.4" + } +} diff --git a/001.FRONTEND/public/css/all.min.css b/001.FRONTEND/public/css/all.min.css new file mode 100644 index 0000000..1f367c1 --- /dev/null +++ b/001.FRONTEND/public/css/all.min.css @@ -0,0 +1,9 @@ +/*! + * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-transition-delay:0s;transition-delay:0s;-webkit-transition-duration:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"} +.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-stubber:before{content:"\e5c7"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-odysee:before{content:"\e5c6"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/001.FRONTEND/public/js/flowbite.min.js b/001.FRONTEND/public/js/flowbite.min.js new file mode 100644 index 0000000..f2663ac --- /dev/null +++ b/001.FRONTEND/public/js/flowbite.min.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("Flowbite",[],e):"object"==typeof exports?exports.Flowbite=e():t.Flowbite=e()}(self,(function(){return function(){"use strict";var t={647:function(t,e,i){i.r(e)},853:function(t,e,i){i.r(e),i.d(e,{afterMain:function(){return k},afterRead:function(){return b},afterWrite:function(){return D},applyStyles:function(){return T},arrow:function(){return Q},auto:function(){return s},basePlacements:function(){return d},beforeMain:function(){return _},beforeRead:function(){return y},beforeWrite:function(){return E},bottom:function(){return r},clippingParents:function(){return u},computeStyles:function(){return it},createPopper:function(){return Tt},createPopperBase:function(){return St},createPopperLite:function(){return Mt},detectOverflow:function(){return mt},end:function(){return l},eventListeners:function(){return rt},flip:function(){return bt},hide:function(){return kt},left:function(){return a},main:function(){return w},modifierPhases:function(){return O},offset:function(){return Et},placements:function(){return v},popper:function(){return p},popperGenerator:function(){return Ct},popperOffsets:function(){return xt},preventOverflow:function(){return Dt},read:function(){return m},reference:function(){return f},right:function(){return o},start:function(){return c},top:function(){return n},variationPlacements:function(){return g},viewport:function(){return h},write:function(){return x}});var n="top",r="bottom",o="right",a="left",s="auto",d=[n,r,o,a],c="start",l="end",u="clippingParents",h="viewport",p="popper",f="reference",g=d.reduce((function(t,e){return t.concat([e+"-"+c,e+"-"+l])}),[]),v=[].concat(d,[s]).reduce((function(t,e){return t.concat([e,e+"-"+c,e+"-"+l])}),[]),y="beforeRead",m="read",b="afterRead",_="beforeMain",w="main",k="afterMain",E="beforeWrite",x="write",D="afterWrite",O=[y,m,b,_,w,k,E,x,D];function L(t){return t?(t.nodeName||"").toLowerCase():null}function I(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function A(t){return t instanceof I(t).Element||t instanceof Element}function C(t){return t instanceof I(t).HTMLElement||t instanceof HTMLElement}function S(t){return"undefined"!=typeof ShadowRoot&&(t instanceof I(t).ShadowRoot||t instanceof ShadowRoot)}var T={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},r=e.elements[t];C(r)&&L(r)&&(Object.assign(r.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?r.removeAttribute(t):r.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],r=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});C(n)&&L(n)&&(Object.assign(n.style,o),Object.keys(r).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function M(t){return t.split("-")[0]}var H=Math.max,P=Math.min,j=Math.round;function V(){var t=navigator.userAgentData;return null!=t&&t.brands?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function B(){return!/^((?!chrome|android).)*safari/i.test(V())}function z(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),r=1,o=1;e&&C(t)&&(r=t.offsetWidth>0&&j(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&j(n.height)/t.offsetHeight||1);var a=(A(t)?I(t):window).visualViewport,s=!B()&&i,d=(n.left+(s&&a?a.offsetLeft:0))/r,c=(n.top+(s&&a?a.offsetTop:0))/o,l=n.width/r,u=n.height/o;return{width:l,height:u,top:c,right:d+l,bottom:c+u,left:d,x:d,y:c}}function F(t){var e=z(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function N(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&S(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function W(t){return I(t).getComputedStyle(t)}function q(t){return["table","td","th"].indexOf(L(t))>=0}function R(t){return((A(t)?t.ownerDocument:t.document)||window.document).documentElement}function Y(t){return"html"===L(t)?t:t.assignedSlot||t.parentNode||(S(t)?t.host:null)||R(t)}function K(t){return C(t)&&"fixed"!==W(t).position?t.offsetParent:null}function U(t){for(var e=I(t),i=K(t);i&&q(i)&&"static"===W(i).position;)i=K(i);return i&&("html"===L(i)||"body"===L(i)&&"static"===W(i).position)?e:i||function(t){var e=/firefox/i.test(V());if(/Trident/i.test(V())&&C(t)&&"fixed"===W(t).position)return null;var i=Y(t);for(S(i)&&(i=i.host);C(i)&&["html","body"].indexOf(L(i))<0;){var n=W(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function J(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return H(t,P(e,i))}function $(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Q={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,s=t.name,c=t.options,l=i.elements.arrow,u=i.modifiersData.popperOffsets,h=M(i.placement),p=J(h),f=[a,o].indexOf(h)>=0?"height":"width";if(l&&u){var g=function(t,e){return $("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,d))}(c.padding,i),v=F(l),y="y"===p?n:a,m="y"===p?r:o,b=i.rects.reference[f]+i.rects.reference[p]-u[p]-i.rects.popper[f],_=u[p]-i.rects.reference[p],w=U(l),k=w?"y"===p?w.clientHeight||0:w.clientWidth||0:0,E=b/2-_/2,x=g[y],D=k-v[f]-g[m],O=k/2-v[f]/2+E,L=X(x,O,D),I=p;i.modifiersData[s]=((e={})[I]=L,e.centerOffset=L-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&N(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,s=t.popperRect,d=t.placement,c=t.variation,u=t.offsets,h=t.position,p=t.gpuAcceleration,f=t.adaptive,g=t.roundOffsets,v=t.isFixed,y=u.x,m=void 0===y?0:y,b=u.y,_=void 0===b?0:b,w="function"==typeof g?g({x:m,y:_}):{x:m,y:_};m=w.x,_=w.y;var k=u.hasOwnProperty("x"),E=u.hasOwnProperty("y"),x=a,D=n,O=window;if(f){var L=U(i),A="clientHeight",C="clientWidth";if(L===I(i)&&"static"!==W(L=R(i)).position&&"absolute"===h&&(A="scrollHeight",C="scrollWidth"),d===n||(d===a||d===o)&&c===l)D=r,_-=(v&&L===O&&O.visualViewport?O.visualViewport.height:L[A])-s.height,_*=p?1:-1;if(d===a||(d===n||d===r)&&c===l)x=o,m-=(v&&L===O&&O.visualViewport?O.visualViewport.width:L[C])-s.width,m*=p?1:-1}var S,T=Object.assign({position:h},f&&tt),M=!0===g?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:j(e*n)/n||0,y:j(i*n)/n||0}}({x:m,y:_}):{x:m,y:_};return m=M.x,_=M.y,p?Object.assign({},T,((S={})[D]=E?"0":"",S[x]=k?"0":"",S.transform=(O.devicePixelRatio||1)<=1?"translate("+m+"px, "+_+"px)":"translate3d("+m+"px, "+_+"px, 0)",S)):Object.assign({},T,((e={})[D]=E?_+"px":"",e[x]=k?m+"px":"",e.transform="",e))}var it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,r=void 0===n||n,o=i.adaptive,a=void 0===o||o,s=i.roundOffsets,d=void 0===s||s,c={placement:M(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:r,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:a,roundOffsets:d})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:d})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},nt={passive:!0};var rt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,r=n.scroll,o=void 0===r||r,a=n.resize,s=void 0===a||a,d=I(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),s&&d.addEventListener("resize",i.update,nt),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),s&&d.removeEventListener("resize",i.update,nt)}},data:{}},ot={left:"right",right:"left",bottom:"top",top:"bottom"};function at(t){return t.replace(/left|right|bottom|top/g,(function(t){return ot[t]}))}var st={start:"end",end:"start"};function dt(t){return t.replace(/start|end/g,(function(t){return st[t]}))}function ct(t){var e=I(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function lt(t){return z(R(t)).left+ct(t).scrollLeft}function ut(t){var e=W(t),i=e.overflow,n=e.overflowX,r=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+r+n)}function ht(t){return["html","body","#document"].indexOf(L(t))>=0?t.ownerDocument.body:C(t)&&ut(t)?t:ht(Y(t))}function pt(t,e){var i;void 0===e&&(e=[]);var n=ht(t),r=n===(null==(i=t.ownerDocument)?void 0:i.body),o=I(n),a=r?[o].concat(o.visualViewport||[],ut(n)?n:[]):n,s=e.concat(a);return r?s:s.concat(pt(Y(a)))}function ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function gt(t,e,i){return e===h?ft(function(t,e){var i=I(t),n=R(t),r=i.visualViewport,o=n.clientWidth,a=n.clientHeight,s=0,d=0;if(r){o=r.width,a=r.height;var c=B();(c||!c&&"fixed"===e)&&(s=r.offsetLeft,d=r.offsetTop)}return{width:o,height:a,x:s+lt(t),y:d}}(t,i)):A(e)?function(t,e){var i=z(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ft(function(t){var e,i=R(t),n=ct(t),r=null==(e=t.ownerDocument)?void 0:e.body,o=H(i.scrollWidth,i.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),a=H(i.scrollHeight,i.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),s=-n.scrollLeft+lt(t),d=-n.scrollTop;return"rtl"===W(r||i).direction&&(s+=H(i.clientWidth,r?r.clientWidth:0)-o),{width:o,height:a,x:s,y:d}}(R(t)))}function vt(t,e,i,n){var r="clippingParents"===e?function(t){var e=pt(Y(t)),i=["absolute","fixed"].indexOf(W(t).position)>=0&&C(t)?U(t):t;return A(i)?e.filter((function(t){return A(t)&&N(t,i)&&"body"!==L(t)})):[]}(t):[].concat(e),o=[].concat(r,[i]),a=o[0],s=o.reduce((function(e,i){var r=gt(t,i,n);return e.top=H(r.top,e.top),e.right=P(r.right,e.right),e.bottom=P(r.bottom,e.bottom),e.left=H(r.left,e.left),e}),gt(t,a,n));return s.width=s.right-s.left,s.height=s.bottom-s.top,s.x=s.left,s.y=s.top,s}function yt(t){var e,i=t.reference,s=t.element,d=t.placement,u=d?M(d):null,h=d?Z(d):null,p=i.x+i.width/2-s.width/2,f=i.y+i.height/2-s.height/2;switch(u){case n:e={x:p,y:i.y-s.height};break;case r:e={x:p,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:f};break;case a:e={x:i.x-s.width,y:f};break;default:e={x:i.x,y:i.y}}var g=u?J(u):null;if(null!=g){var v="y"===g?"height":"width";switch(h){case c:e[g]=e[g]-(i[v]/2-s[v]/2);break;case l:e[g]=e[g]+(i[v]/2-s[v]/2)}}return e}function mt(t,e){void 0===e&&(e={});var i=e,a=i.placement,s=void 0===a?t.placement:a,c=i.strategy,l=void 0===c?t.strategy:c,g=i.boundary,v=void 0===g?u:g,y=i.rootBoundary,m=void 0===y?h:y,b=i.elementContext,_=void 0===b?p:b,w=i.altBoundary,k=void 0!==w&&w,E=i.padding,x=void 0===E?0:E,D=$("number"!=typeof x?x:G(x,d)),O=_===p?f:p,L=t.rects.popper,I=t.elements[k?O:_],C=vt(A(I)?I:I.contextElement||R(t.elements.popper),v,m,l),S=z(t.elements.reference),T=yt({reference:S,element:L,strategy:"absolute",placement:s}),M=ft(Object.assign({},L,T)),H=_===p?M:S,P={top:C.top-H.top+D.top,bottom:H.bottom-C.bottom+D.bottom,left:C.left-H.left+D.left,right:H.right-C.right+D.right},j=t.modifiersData.offset;if(_===p&&j){var V=j[s];Object.keys(P).forEach((function(t){var e=[o,r].indexOf(t)>=0?1:-1,i=[n,r].indexOf(t)>=0?"y":"x";P[t]+=V[i]*e}))}return P}var bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,l=t.name;if(!e.modifiersData[l]._skip){for(var u=i.mainAxis,h=void 0===u||u,p=i.altAxis,f=void 0===p||p,y=i.fallbackPlacements,m=i.padding,b=i.boundary,_=i.rootBoundary,w=i.altBoundary,k=i.flipVariations,E=void 0===k||k,x=i.allowedAutoPlacements,D=e.options.placement,O=M(D),L=y||(O===D||!E?[at(D)]:function(t){if(M(t)===s)return[];var e=at(t);return[dt(t),e,dt(e)]}(D)),I=[D].concat(L).reduce((function(t,i){return t.concat(M(i)===s?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,r=i.boundary,o=i.rootBoundary,a=i.padding,s=i.flipVariations,c=i.allowedAutoPlacements,l=void 0===c?v:c,u=Z(n),h=u?s?g:g.filter((function(t){return Z(t)===u})):d,p=h.filter((function(t){return l.indexOf(t)>=0}));0===p.length&&(p=h);var f=p.reduce((function(e,i){return e[i]=mt(t,{placement:i,boundary:r,rootBoundary:o,padding:a})[M(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}(e,{placement:i,boundary:b,rootBoundary:_,padding:m,flipVariations:E,allowedAutoPlacements:x}):i)}),[]),A=e.rects.reference,C=e.rects.popper,S=new Map,T=!0,H=I[0],P=0;P=0,F=z?"width":"height",N=mt(e,{placement:j,boundary:b,rootBoundary:_,altBoundary:w,padding:m}),W=z?B?o:a:B?r:n;A[F]>C[F]&&(W=at(W));var q=at(W),R=[];if(h&&R.push(N[V]<=0),f&&R.push(N[W]<=0,N[q]<=0),R.every((function(t){return t}))){H=j,T=!1;break}S.set(j,R)}if(T)for(var Y=function(t){var e=I.find((function(e){var i=S.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return H=e,"break"},K=E?3:1;K>0;K--){if("break"===Y(K))break}e.placement!==H&&(e.modifiersData[l]._skip=!0,e.placement=H,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function _t(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function wt(t){return[n,o,r,a].some((function(e){return t[e]>=0}))}var kt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,r=e.rects.popper,o=e.modifiersData.preventOverflow,a=mt(e,{elementContext:"reference"}),s=mt(e,{altBoundary:!0}),d=_t(a,n),c=_t(s,r,o),l=wt(d),u=wt(c);e.modifiersData[i]={referenceClippingOffsets:d,popperEscapeOffsets:c,isReferenceHidden:l,hasPopperEscaped:u},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":l,"data-popper-escaped":u})}};var Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,r=t.name,s=i.offset,d=void 0===s?[0,0]:s,c=v.reduce((function(t,i){return t[i]=function(t,e,i){var r=M(t),s=[a,n].indexOf(r)>=0?-1:1,d="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=d[0],l=d[1];return c=c||0,l=(l||0)*s,[a,o].indexOf(r)>=0?{x:l,y:c}:{x:c,y:l}}(i,e.rects,d),t}),{}),l=c[e.placement],u=l.x,h=l.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=u,e.modifiersData.popperOffsets.y+=h),e.modifiersData[r]=c}};var xt={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=yt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}};var Dt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,s=t.name,d=i.mainAxis,l=void 0===d||d,u=i.altAxis,h=void 0!==u&&u,p=i.boundary,f=i.rootBoundary,g=i.altBoundary,v=i.padding,y=i.tether,m=void 0===y||y,b=i.tetherOffset,_=void 0===b?0:b,w=mt(e,{boundary:p,rootBoundary:f,padding:v,altBoundary:g}),k=M(e.placement),E=Z(e.placement),x=!E,D=J(k),O="x"===D?"y":"x",L=e.modifiersData.popperOffsets,I=e.rects.reference,A=e.rects.popper,C="function"==typeof _?_(Object.assign({},e.rects,{placement:e.placement})):_,S="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),T=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,j={x:0,y:0};if(L){if(l){var V,B="y"===D?n:a,z="y"===D?r:o,N="y"===D?"height":"width",W=L[D],q=W+w[B],R=W-w[z],Y=m?-A[N]/2:0,K=E===c?I[N]:A[N],$=E===c?-A[N]:-I[N],G=e.elements.arrow,Q=m&&G?F(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[B],it=tt[z],nt=X(0,I[N],Q[N]),rt=x?I[N]/2-Y-nt-et-S.mainAxis:K-nt-et-S.mainAxis,ot=x?-I[N]/2+Y+nt+it+S.mainAxis:$+nt+it+S.mainAxis,at=e.elements.arrow&&U(e.elements.arrow),st=at?"y"===D?at.clientTop||0:at.clientLeft||0:0,dt=null!=(V=null==T?void 0:T[D])?V:0,ct=W+ot-dt,lt=X(m?P(q,W+rt-dt-st):q,W,m?H(R,ct):R);L[D]=lt,j[D]=lt-W}if(h){var ut,ht="x"===D?n:a,pt="x"===D?r:o,ft=L[O],gt="y"===O?"height":"width",vt=ft+w[ht],yt=ft-w[pt],bt=-1!==[n,a].indexOf(k),_t=null!=(ut=null==T?void 0:T[O])?ut:0,wt=bt?vt:ft-I[gt]-A[gt]-_t+S.altAxis,kt=bt?ft+I[gt]+A[gt]-_t-S.altAxis:yt,Et=m&&bt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,ft,kt):X(m?wt:vt,ft,m?kt:yt);L[O]=Et,j[O]=Et-ft}e.modifiersData[s]=j}},requiresIfExists:["offset"]};function Ot(t,e,i){void 0===i&&(i=!1);var n,r,o=C(e),a=C(e)&&function(t){var e=t.getBoundingClientRect(),i=j(e.width)/t.offsetWidth||1,n=j(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),s=R(e),d=z(t,a,i),c={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(o||!o&&!i)&&(("body"!==L(e)||ut(s))&&(c=(n=e)!==I(n)&&C(n)?{scrollLeft:(r=n).scrollLeft,scrollTop:r.scrollTop}:ct(n)),C(e)?((l=z(e,!0)).x+=e.clientLeft,l.y+=e.clientTop):s&&(l.x=lt(s))),{x:d.left+c.scrollLeft-l.x,y:d.top+c.scrollTop-l.y,width:d.width,height:d.height}}function Lt(t){var e=new Map,i=new Set,n=[];function r(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&r(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||r(t)})),n}var It={placement:"bottom",modifiers:[],strategy:"absolute"};function At(){for(var t=arguments.length,e=new Array(t),i=0;it.length)&&(e=t.length);for(var i=0,n=Array(e);i1?e-1:0),n=1;n=e)&&(void 0===i||t<=i)}function E(t,e,i){return ti?i:t}function x(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"",o=Object.keys(i).reduce((function(t,e){var r=i[e];return"function"==typeof r&&(r=r(n)),"".concat(t," ").concat(e,'="').concat(r,'"')}),t);r+="<".concat(o,">");var a=n+1;return a\s+/g,">").replace(/\s+2&&void 0!==arguments[2]?arguments[2]:0,n=new Date(t).getDay();return A(t,T(e,i)-T(n,i))}function H(t,e){var i=new Date(t).getFullYear();return Math.floor(i/e)*e}Object.defineProperty(e,"__esModule",{value:!0});var P=/dd?|DD?|mm?|MM?|yy?(?:yy)?/,j=/[\s!-/:-@[-`{-~年月日]+/,V={},B={y:function(t,e){return new Date(t).setFullYear(parseInt(e,10))},m:function(t,e,i){var n=new Date(t),r=parseInt(e,10)-1;if(isNaN(r)){if(!e)return NaN;var o=e.toLowerCase(),a=function(t){return t.toLowerCase().startsWith(o)};if((r=i.monthsShort.findIndex(a))<0&&(r=i.months.findIndex(a)),r<0)return NaN}return n.setMonth(r),n.getMonth()!==F(r)?n.setDate(0):n.getTime()},d:function(t,e){return new Date(t).setDate(parseInt(e,10))}},z={d:function(t){return t.getDate()},dd:function(t){return N(t.getDate(),2)},D:function(t,e){return e.daysShort[t.getDay()]},DD:function(t,e){return e.days[t.getDay()]},m:function(t){return t.getMonth()+1},mm:function(t){return N(t.getMonth()+1,2)},M:function(t,e){return e.monthsShort[t.getMonth()]},MM:function(t,e){return e.months[t.getMonth()]},y:function(t){return t.getFullYear()},yy:function(t){return N(t.getFullYear(),2).slice(-2)},yyyy:function(t){return N(t.getFullYear(),4)}};function F(t){return t>-1?t%12:F(t+12)}function N(t,e){return t.toString().padStart(e,"0")}function W(t){if("string"!=typeof t)throw new Error("Invalid date format.");if(t in V)return V[t];var e=t.split(P),i=t.match(new RegExp(P,"g"));if(0===e.length||!i)throw new Error("Invalid date format.");var n=i.map((function(t){return z[t]})),r=Object.keys(B).reduce((function(t,e){return i.find((function(t){return"D"!==t[0]&&t[0].toLowerCase()===e}))&&t.push(e),t}),[]);return V[t]={parser:function(t,e){var n=t.split(j).reduce((function(t,e,n){if(e.length>0&&i[n]){var r=i[n][0];"M"===r?t.m=e:"D"!==r&&(t[r]=e)}return t}),{});return r.reduce((function(t,i){var r=B[i](t,n[i],e);return isNaN(r)?t:r}),L())},formatter:function(t,i){return n.reduce((function(n,r,o){return n+"".concat(e[o]).concat(r(t,i))}),"")+b(e)}}}function q(t,e,i){if(t instanceof Date||"number"==typeof t){var n=O(t);return isNaN(n)?void 0:n}if(t){if("today"===t)return L();if(e&&e.toValue){var r=e.toValue(t,e,i);return isNaN(r)?void 0:O(r)}return W(e).parser(t,i)}}function R(t,e,i){if(isNaN(t)||!t&&0!==t)return"";var n="number"==typeof t?new Date(t):t;return e.toDisplay?e.toDisplay(n,e,i):W(e).formatter(n,i)}var Y=new WeakMap,K=EventTarget.prototype,U=K.addEventListener,J=K.removeEventListener;function X(t,e){var i=Y.get(t);i||(i=[],Y.set(t,i)),e.forEach((function(t){U.call.apply(U,f(t)),i.push(t)}))}function $(t){var e=Y.get(t);e&&(e.forEach((function(t){J.call.apply(J,f(t))})),Y.delete(t))}if(!Event.prototype.composedPath){var G=function t(e){var i,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];return n.push(e),e.parentNode?i=e.parentNode:e.host?i=e.host:e.defaultView&&(i=e.defaultView),i?t(i,n):n};Event.prototype.composedPath=function(){return G(this.target)}}function Q(t,e,i){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,r=t[n];return e(r)?r:r!==i&&r.parentElement?Q(t,e,i,n+1):void 0}function Z(t,e){var i="function"==typeof e?e:function(t){return t.matches(e)};return Q(t.composedPath(),i,t.currentTarget)}var tt={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear",titleFormat:"MM y"}},et={autohide:!1,beforeShowDay:null,beforeShowDecade:null,beforeShowMonth:null,beforeShowYear:null,calendarWeeks:!1,clearBtn:!1,dateDelimiter:",",datesDisabled:[],daysOfWeekDisabled:[],daysOfWeekHighlighted:[],defaultViewDate:void 0,disableTouchKeyboard:!1,format:"mm/dd/yyyy",language:"en",maxDate:null,maxNumberOfDates:1,maxView:3,minDate:null,nextArrow:'',orientation:"auto",pickLevel:0,prevArrow:'',showDaysOfWeek:!0,showOnClick:!0,showOnFocus:!0,startView:0,title:"",todayBtn:!1,todayBtnMode:0,todayHighlight:!1,updateOnBlur:!0,weekStart:0},it=document.createRange();function nt(t){return it.createContextualFragment(t)}function rt(t){"none"!==t.style.display&&(t.style.display&&(t.dataset.styleDisplay=t.style.display),t.style.display="none")}function ot(t){"none"===t.style.display&&(t.dataset.styleDisplay?(t.style.display=t.dataset.styleDisplay,delete t.dataset.styleDisplay):t.style.display="")}function at(t){t.firstChild&&(t.removeChild(t.firstChild),at(t))}var st=et.language,dt=et.format,ct=et.weekStart;function lt(t,e){return t.length<6&&e>=0&&e<7?_(t,e):t}function ut(t){return(t+6)%7}function ht(t,e,i,n){var r=q(t,e,i);return void 0!==r?r:n}function pt(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3,n=parseInt(t,10);return n>=0&&n<=i?n:e}function ft(t,e){var i,n=Object.assign({},t),r={},o=e.constructor.locales,a=e.config||{},s=a.format,d=a.language,c=a.locale,l=a.maxDate,u=a.maxView,h=a.minDate,p=a.pickLevel,f=a.startView,g=a.weekStart;if(n.language&&(n.language!==d&&(o[n.language]?i=n.language:void 0===o[i=n.language.split("-")[0]]&&(i=!1)),delete n.language,i)){d=r.language=i;var v=c||o[st];c=Object.assign({format:dt,weekStart:ct},o[st]),d!==st&&Object.assign(c,o[d]),r.locale=c,s===v.format&&(s=r.format=c.format),g===v.weekStart&&(g=r.weekStart=c.weekStart,r.weekEnd=ut(c.weekStart))}if(n.format){var y="function"==typeof n.format.toDisplay,b="function"==typeof n.format.toValue,w=P.test(n.format);(y&&b||w)&&(s=r.format=n.format),delete n.format}var k=h,E=l;if(void 0!==n.minDate&&(k=null===n.minDate?I(0,0,1):ht(n.minDate,s,c,k),delete n.minDate),void 0!==n.maxDate&&(E=null===n.maxDate?void 0:ht(n.maxDate,s,c,E),delete n.maxDate),E=0&&(r.maxNumberOfDates=O,r.multidate=1!==O),delete n.maxNumberOfDates}n.dateDelimiter&&(r.dateDelimiter=String(n.dateDelimiter),delete n.dateDelimiter);var L=p;void 0!==n.pickLevel&&(L=pt(n.pickLevel,2),delete n.pickLevel),L!==p&&(p=r.pickLevel=L);var A=u;void 0!==n.maxView&&(A=pt(n.maxView,u),delete n.maxView),(A=p>A?p:A)!==u&&(u=r.maxView=A);var C=f;if(void 0!==n.startView&&(C=pt(n.startView,C),delete n.startView),Cu&&(C=u),C!==f&&(r.startView=C),n.prevArrow){var S=nt(n.prevArrow);S.childNodes.length>0&&(r.prevArrow=S.childNodes),delete n.prevArrow}if(n.nextArrow){var T=nt(n.nextArrow);T.childNodes.length>0&&(r.nextArrow=T.childNodes),delete n.nextArrow}if(void 0!==n.disableTouchKeyboard&&(r.disableTouchKeyboard="ontouchstart"in document&&!!n.disableTouchKeyboard,delete n.disableTouchKeyboard),n.orientation){var M=n.orientation.toLowerCase().split(/\s+/g);r.orientation={x:M.find((function(t){return"left"===t||"right"===t}))||"auto",y:M.find((function(t){return"top"===t||"bottom"===t}))||"auto"},delete n.orientation}if(void 0!==n.todayBtnMode){switch(n.todayBtnMode){case 0:case 1:r.todayBtnMode=n.todayBtnMode}delete n.todayBtnMode}return Object.keys(n).forEach((function(t){void 0!==n[t]&&m(et,t)&&(r[t]=n[t])})),r}var gt=D(''),vt=D('
\n
'.concat(x("span",7,{class:"dow block flex-1 leading-9 border-0 rounded-lg cursor-default text-center text-gray-900 font-semibold text-sm"}),'
\n
').concat(x("span",42,{class:"block flex-1 leading-9 border-0 rounded-lg cursor-default text-center text-gray-900 font-semibold text-sm h-6 leading-6 text-sm font-medium text-gray-500 dark:text-gray-400"}),"
\n
")),yt=D('
\n
\n
'.concat(x("span",6,{class:"week block flex-1 leading-9 border-0 rounded-lg cursor-default text-center text-gray-900 font-semibold text-sm"}),"
\n
")),mt=function(){return a((function t(e,i){r(this,t),Object.assign(this,i,{picker:e,element:nt('
').firstChild,selected:[]}),this.init(this.picker.datepicker.config)}),[{key:"init",value:function(t){void 0!==t.pickLevel&&(this.isMinView=this.id===t.pickLevel),this.setOptions(t),this.updateFocus(),this.updateSelection()}},{key:"performBeforeHook",value:function(t,e,i){var n=this.beforeShow(new Date(i));switch(v(n)){case"boolean":n={enabled:n};break;case"string":n={classes:n}}if(n){if(!1===n.enabled&&(t.classList.add("disabled"),_(this.disabled,e)),n.classes){var r,o=n.classes.split(/\s+/);(r=t.classList).add.apply(r,f(o)),o.includes("disabled")&&_(this.disabled,e)}n.content&&function(t,e){at(t),e instanceof DocumentFragment?t.appendChild(e):"string"==typeof e?t.appendChild(nt(e)):"function"==typeof e.forEach&&e.forEach((function(e){t.appendChild(e)}))}(t,n.content)}}}])}(),bt=function(t){function e(t){return r(this,e),n(this,e,[t,{id:0,name:"days",cellClass:"day"}])}return c(e,t),a(e,[{key:"init",value:function(t){var i=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(i){var n=nt(vt).firstChild;this.dow=n.firstChild,this.grid=n.lastChild,this.element.appendChild(n)}s(d(e.prototype),"init",this).call(this,t)}},{key:"setOptions",value:function(t){var e,i=this;if(m(t,"minDate")&&(this.minDate=t.minDate),m(t,"maxDate")&&(this.maxDate=t.maxDate),t.datesDisabled&&(this.datesDisabled=t.datesDisabled),t.daysOfWeekDisabled&&(this.daysOfWeekDisabled=t.daysOfWeekDisabled,e=!0),t.daysOfWeekHighlighted&&(this.daysOfWeekHighlighted=t.daysOfWeekHighlighted),void 0!==t.todayHighlight&&(this.todayHighlight=t.todayHighlight),void 0!==t.weekStart&&(this.weekStart=t.weekStart,this.weekEnd=t.weekEnd,e=!0),t.locale){var n=this.locale=t.locale;this.dayNames=n.daysMin,this.switchLabelFormat=n.titleFormat,e=!0}if(void 0!==t.beforeShowDay&&(this.beforeShow="function"==typeof t.beforeShowDay?t.beforeShowDay:void 0),void 0!==t.calendarWeeks)if(t.calendarWeeks&&!this.calendarWeeks){var r=nt(yt).firstChild;this.calendarWeeks={element:r,dow:r.firstChild,weeks:r.lastChild},this.element.insertBefore(r,this.element.firstChild)}else this.calendarWeeks&&!t.calendarWeeks&&(this.element.removeChild(this.calendarWeeks.element),this.calendarWeeks=null);void 0!==t.showDaysOfWeek&&(t.showDaysOfWeek?(ot(this.dow),this.calendarWeeks&&ot(this.calendarWeeks.dow)):(rt(this.dow),this.calendarWeeks&&rt(this.calendarWeeks.dow))),e&&Array.from(this.dow.children).forEach((function(t,e){var n=(i.weekStart+e)%7;t.textContent=i.dayNames[n],t.className=i.daysOfWeekDisabled.includes(n)?"dow disabled text-center h-6 leading-6 text-sm font-medium text-gray-500 dark:text-gray-400 cursor-not-allowed":"dow text-center h-6 leading-6 text-sm font-medium text-gray-500 dark:text-gray-400"}))}},{key:"updateFocus",value:function(){var t=new Date(this.picker.viewDate),e=t.getFullYear(),i=t.getMonth(),n=I(e,i,1),r=M(n,this.weekStart,this.weekStart);this.first=n,this.last=I(e,i+1,0),this.start=r,this.focused=this.picker.viewDate}},{key:"updateSelection",value:function(){var t=this.picker.datepicker,e=t.dates,i=t.rangepicker;this.selected=e,i&&(this.range=i.dates)}},{key:"render",value:function(){var t=this;this.today=this.todayHighlight?L():void 0,this.disabled=f(this.datesDisabled);var e=R(this.focused,this.switchLabelFormat,this.locale);if(this.picker.setViewSwitchLabel(e),this.picker.setPrevBtnDisabled(this.first<=this.minDate),this.picker.setNextBtnDisabled(this.last>=this.maxDate),this.calendarWeeks){var i=M(this.first,1,1);Array.from(this.calendarWeeks.weeks.children).forEach((function(t,e){t.textContent=function(t){var e=M(t,4,1),i=M(new Date(e).setMonth(0,4),4,1);return Math.round((e-i)/6048e5)+1}(A(i,7*e))}))}Array.from(this.grid.children).forEach((function(e,i){var n=e.classList,r=A(t.start,i),o=new Date(r),a=o.getDay();if(e.className="datepicker-cell hover:bg-gray-100 dark:hover:bg-gray-600 block flex-1 leading-9 border-0 rounded-lg cursor-pointer text-center text-gray-900 dark:text-white font-semibold text-sm ".concat(t.cellClass),e.dataset.date=r,e.textContent=o.getDate(),rt.last&&n.add("next","text-gray-500","dark:text-white"),t.today===r&&n.add("today","bg-gray-100","dark:bg-gray-600"),(rt.maxDate||t.disabled.includes(r))&&(n.add("disabled","cursor-not-allowed","text-gray-400","dark:text-gray-500"),n.remove("hover:bg-gray-100","dark:hover:bg-gray-600","text-gray-900","dark:text-white","cursor-pointer")),t.daysOfWeekDisabled.includes(a)&&(n.add("disabled","cursor-not-allowed","text-gray-400","dark:text-gray-500"),n.remove("hover:bg-gray-100","dark:hover:bg-gray-600","text-gray-900","dark:text-white","cursor-pointer"),_(t.disabled,r)),t.daysOfWeekHighlighted.includes(a)&&n.add("highlighted"),t.range){var s=h(t.range,2),d=s[0],c=s[1];r>d&&ri&&re||s1&&void 0!==arguments[1])||arguments[1];i&&(this.grid=this.element,this.element.classList.add("months","datepicker-grid","w-64","grid","grid-cols-4"),this.grid.appendChild(nt(x("span",12,{"data-month":function(t){return t}})))),s(d(e.prototype),"init",this).call(this,t)}},{key:"setOptions",value:function(t){if(t.locale&&(this.monthNames=t.locale.monthsShort),m(t,"minDate"))if(void 0===t.minDate)this.minYear=this.minMonth=this.minDate=void 0;else{var e=new Date(t.minDate);this.minYear=e.getFullYear(),this.minMonth=e.getMonth(),this.minDate=e.setDate(1)}if(m(t,"maxDate"))if(void 0===t.maxDate)this.maxYear=this.maxMonth=this.maxDate=void 0;else{var i=new Date(t.maxDate);this.maxYear=i.getFullYear(),this.maxMonth=i.getMonth(),this.maxDate=I(this.maxYear,this.maxMonth+1,0)}void 0!==t.beforeShowMonth&&(this.beforeShow="function"==typeof t.beforeShowMonth?t.beforeShowMonth:void 0)}},{key:"updateFocus",value:function(){var t=new Date(this.picker.viewDate);this.year=t.getFullYear(),this.focused=t.getMonth()}},{key:"updateSelection",value:function(){var t=this.picker.datepicker,e=t.dates,i=t.rangepicker;this.selected=e.reduce((function(t,e){var i=new Date(e),n=i.getFullYear(),r=i.getMonth();return void 0===t[n]?t[n]=[r]:_(t[n],r),t}),{}),i&&i.dates&&(this.range=i.dates.map((function(t){var e=new Date(t);return isNaN(e)?void 0:[e.getFullYear(),e.getMonth()]})))}},{key:"render",value:function(){var t=this;this.disabled=[],this.picker.setViewSwitchLabel(this.year),this.picker.setPrevBtnDisabled(this.year<=this.minYear),this.picker.setNextBtnDisabled(this.year>=this.maxYear);var e=this.selected[this.year]||[],i=this.yearthis.maxYear,n=this.year===this.minYear,r=this.year===this.maxYear,o=_t(this.range,this.year);Array.from(this.grid.children).forEach((function(a,s){var d=a.classList,c=I(t.year,s,1);if(a.className="datepicker-cell hover:bg-gray-100 dark:hover:bg-gray-600 block flex-1 leading-9 border-0 rounded-lg cursor-pointer text-center text-gray-900 dark:text-white font-semibold text-sm ".concat(t.cellClass),t.isMinView&&(a.dataset.date=c),a.textContent=t.monthNames[s],(i||n&&st.maxMonth)&&d.add("disabled"),o){var l=h(o,2),u=l[0],p=l[1];s>u&&sn&&o1&&void 0!==arguments[1])||arguments[1];i&&(this.navStep=10*this.step,this.beforeShowOption="beforeShow".concat(kt(this.cellClass)),this.grid=this.element,this.element.classList.add(this.name,"datepicker-grid","w-64","grid","grid-cols-4"),this.grid.appendChild(nt(x("span",12)))),s(d(e.prototype),"init",this).call(this,t)}},{key:"setOptions",value:function(t){if(m(t,"minDate")&&(void 0===t.minDate?this.minYear=this.minDate=void 0:(this.minYear=H(t.minDate,this.step),this.minDate=I(this.minYear,0,1))),m(t,"maxDate")&&(void 0===t.maxDate?this.maxYear=this.maxDate=void 0:(this.maxYear=H(t.maxDate,this.step),this.maxDate=I(this.maxYear,11,31))),void 0!==t[this.beforeShowOption]){var e=t[this.beforeShowOption];this.beforeShow="function"==typeof e?e:void 0}}},{key:"updateFocus",value:function(){var t=new Date(this.picker.viewDate),e=H(t,this.navStep),i=e+9*this.step;this.first=e,this.last=i,this.start=e-this.step,this.focused=H(t,this.step)}},{key:"updateSelection",value:function(){var t=this,e=this.picker.datepicker,i=e.dates,n=e.rangepicker;this.selected=i.reduce((function(e,i){return _(e,H(i,t.step))}),[]),n&&n.dates&&(this.range=n.dates.map((function(e){if(void 0!==e)return H(e,t.step)})))}},{key:"render",value:function(){var t=this;this.disabled=[],this.picker.setViewSwitchLabel("".concat(this.first,"-").concat(this.last)),this.picker.setPrevBtnDisabled(this.first<=this.minYear),this.picker.setNextBtnDisabled(this.last>=this.maxYear),Array.from(this.grid.children).forEach((function(e,i){var n=e.classList,r=t.start+i*t.step,o=I(r,0,1);if(e.className="datepicker-cell hover:bg-gray-100 dark:hover:bg-gray-600 block flex-1 leading-9 border-0 rounded-lg cursor-pointer text-center text-gray-900 dark:text-white font-semibold text-sm ".concat(t.cellClass),t.isMinView&&(e.dataset.date=o),e.textContent=e.dataset.year=r,0===i?n.add("prev"):11===i&&n.add("next"),(rt.maxYear)&&n.add("disabled"),t.range){var a=h(t.range,2),s=a[0],d=a[1];r>s&&ri&&r0?b(e):i.defaultViewDate,i.minDate,i.maxDate)}function Bt(t,e){var i=new Date(t.viewDate),n=new Date(e),r=t.currentView,o=r.id,a=r.year,s=r.first,d=r.last,c=n.getFullYear();switch(t.viewDate=e,c!==i.getFullYear()&&xt(t.datepicker,"changeYear"),n.getMonth()!==i.getMonth()&&xt(t.datepicker,"changeMonth"),o){case 0:return ed;case 1:return c!==a;default:return cd}}function zt(t){return window.getComputedStyle(t).direction}var Ft=function(){return a((function t(e){r(this,t),this.datepicker=e;var i=gt.replace(/%buttonClass%/g,e.config.buttonClass),n=this.element=nt(i).firstChild,o=h(n.firstChild.children,3),a=o[0],s=o[1],d=o[2],c=a.firstElementChild,l=h(a.lastElementChild.children,3),u=l[0],p=l[1],f=l[2],g=h(d.firstChild.children,2),v={title:c,prevBtn:u,viewSwitch:p,nextBtn:f,todayBtn:g[0],clearBtn:g[1]};this.main=s,this.controls=v;var y=e.inline?"inline":"dropdown";n.classList.add("datepicker-".concat(y)),"dropdown"===y&&n.classList.add("dropdown","absolute","top-0","left-0","z-50","pt-2"),jt(this,e.config),this.viewDate=Vt(e),X(e,[[n,"click",Pt.bind(null,e),{capture:!0}],[s,"click",Ht.bind(null,e)],[v.viewSwitch,"click",St.bind(null,e)],[v.prevBtn,"click",Tt.bind(null,e)],[v.nextBtn,"click",Mt.bind(null,e)],[v.todayBtn,"click",At.bind(null,e)],[v.clearBtn,"click",Ct.bind(null,e)]]),this.views=[new bt(this),new wt(this),new Et(this,{id:2,name:"years",cellClass:"year",step:1}),new Et(this,{id:3,name:"decades",cellClass:"decade",step:10})],this.currentView=this.views[e.config.startView],this.currentView.render(),this.main.appendChild(this.currentView.element),e.config.container.appendChild(this.element)}),[{key:"setOptions",value:function(t){jt(this,t),this.views.forEach((function(e){e.init(t,!1)})),this.currentView.render()}},{key:"detach",value:function(){this.datepicker.config.container.removeChild(this.element)}},{key:"show",value:function(){if(!this.active){this.element.classList.add("active","block"),this.element.classList.remove("hidden"),this.active=!0;var t=this.datepicker;if(!t.inline){var e=zt(t.inputField);e!==zt(t.config.container)?this.element.dir=e:this.element.dir&&this.element.removeAttribute("dir"),this.place(),t.config.disableTouchKeyboard&&t.inputField.blur()}xt(t,"show")}}},{key:"hide",value:function(){this.active&&(this.datepicker.exitEditMode(),this.element.classList.remove("active","block"),this.element.classList.add("active","block","hidden"),this.active=!1,xt(this.datepicker,"hide"))}},{key:"place",value:function(){var t,e,i,n=this.element,r=n.classList,o=n.style,a=this.datepicker,s=a.config,d=a.inputField,c=s.container,l=this.element.getBoundingClientRect(),u=l.width,h=l.height,p=c.getBoundingClientRect(),f=p.left,g=p.top,v=p.width,y=d.getBoundingClientRect(),m=y.left,b=y.top,_=y.width,w=y.height,k=s.orientation,E=k.x,x=k.y;c===document.body?(t=window.scrollY,e=m+window.scrollX,i=b+t):(e=m-f,i=b-g+(t=c.scrollTop)),"auto"===E&&(e<0?(E="left",e=10):E=e+u>v||"rtl"===zt(d)?"right":"left"),"right"===E&&(e-=u-_),"auto"===x&&(x=i-h0&&void 0!==arguments[0])||arguments[0],e=t&&this._renderMethod||"render";delete this._renderMethod,this.currentView[e]()}}])}();function Nt(t,e,i,n,r,o){if(k(t,r,o))return n(t)?Nt(e(t,i),e,i,n,r,o):t}function Wt(t,e,i,n){var r,o,a=t.picker,s=a.currentView,d=s.step||1,c=a.viewDate;switch(s.id){case 0:c=n?A(c,7*i):e.ctrlKey||e.metaKey?S(c,i):A(c,i),r=A,o=function(t){return s.disabled.includes(t)};break;case 1:c=C(c,n?4*i:i),r=C,o=function(t){var e=new Date(t),i=s.year,n=s.disabled;return e.getFullYear()===i&&n.includes(e.getMonth())};break;default:c=S(c,i*(n?4:1)*d),r=S,o=function(t){return s.disabled.includes(H(t,d))}}void 0!==(c=Nt(c,r,i<0?-d:d,o,s.minDate,s.maxDate))&&a.changeFocus(c).render()}function qt(t,e){if("Tab"!==e.key){var i=t.picker,n=i.currentView,r=n.id,o=n.isMinView;if(i.active)if(t.editMode)switch(e.key){case"Escape":i.hide();break;case"Enter":t.exitEditMode({update:!0,autohide:t.config.autohide});break;default:return}else switch(e.key){case"Escape":i.hide();break;case"ArrowLeft":if(e.ctrlKey||e.metaKey)Dt(t,-1);else{if(e.shiftKey)return void t.enterEditMode();Wt(t,e,-1,!1)}break;case"ArrowRight":if(e.ctrlKey||e.metaKey)Dt(t,1);else{if(e.shiftKey)return void t.enterEditMode();Wt(t,e,1,!1)}break;case"ArrowUp":if(e.ctrlKey||e.metaKey)Ot(t);else{if(e.shiftKey)return void t.enterEditMode();Wt(t,e,-1,!0)}break;case"ArrowDown":if(e.shiftKey&&!e.ctrlKey&&!e.metaKey)return void t.enterEditMode();Wt(t,e,1,!0);break;case"Enter":o?t.setDate(i.viewDate):i.changeView(r-1).render();break;case"Backspace":case"Delete":return void t.enterEditMode();default:return void(1!==e.key.length||e.ctrlKey||e.metaKey||t.enterEditMode())}else switch(e.key){case"ArrowDown":case"Escape":i.show();break;case"Enter":t.update();break;default:return}e.preventDefault(),e.stopPropagation()}else Lt(t)}function Rt(t){t.config.showOnFocus&&!t._showing&&t.show()}function Yt(t,e){var i=e.target;(t.picker.active||t.config.showOnClick)&&(i._active=i===document.activeElement,i._clicking=setTimeout((function(){delete i._active,delete i._clicking}),2e3))}function Kt(t,e){var i=e.target;i._clicking&&(clearTimeout(i._clicking),delete i._clicking,i._active&&t.enterEditMode(),delete i._active,t.config.showOnClick&&t.show())}function Ut(t,e){e.clipboardData.types.includes("text/plain")&&t.enterEditMode()}function Jt(t,e){var i=t.element;if(i===document.activeElement){var n=t.picker.element;Z(e,(function(t){return t===i||t===n}))||Lt(t)}}function Xt(t,e){return t.map((function(t){return R(t,e.format,e.locale)})).join(e.dateDelimiter)}function $t(t,e){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=t.config,r=t.dates,o=t.rangepicker;if(0===e.length)return i?[]:void 0;var a=o&&t===o.datepickers[1],s=e.reduce((function(t,e){var i=q(e,n.format,n.locale);if(void 0===i)return t;if(n.pickLevel>0){var r=new Date(i);i=1===n.pickLevel?a?r.setMonth(r.getMonth()+1,0):r.setDate(1):a?r.setFullYear(r.getFullYear()+1,0,0):r.setMonth(0,1)}return!k(i,n.minDate,n.maxDate)||t.includes(i)||n.datesDisabled.includes(i)||n.daysOfWeekDisabled.includes(new Date(i).getDay())||t.push(i),t}),[]);return 0!==s.length?(n.multidate&&!i&&(s=s.reduce((function(t,e){return r.includes(e)||t.push(e),t}),r.filter((function(t){return!s.includes(t)})))),n.maxNumberOfDates&&s.length>n.maxNumberOfDates?s.slice(-1*n.maxNumberOfDates):s):void 0}function Gt(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],n=t.config,r=t.picker,o=t.inputField;if(2&e){var a=r.active?n.pickLevel:n.startView;r.update().changeView(a).render(i)}1&e&&o&&(o.value=Xt(t.dates,n))}function Qt(t,e,i){var n=i.clear,r=i.render,o=i.autohide;void 0===r&&(r=!0),r?void 0===o&&(o=t.config.autohide):o=!1;var a=$t(t,e,n);a&&(a.toString()!==t.dates.toString()?(t.dates=a,Gt(t,r?3:1),xt(t,"changeDate")):Gt(t,1),o&&t.hide())}var Zt=function(){return a((function t(e){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;r(this,t),e.datepicker=this,this.element=e;var o=this.config=Object.assign({buttonClass:i.buttonClass&&String(i.buttonClass)||"button",container:document.body,defaultViewDate:L(),maxDate:void 0,minDate:void 0},ft(et,this));this._options=i,Object.assign(o,ft(i,this));var a,s,d=this.inline="INPUT"!==e.tagName;if(d)o.container=e,s=w(e.dataset.date,o.dateDelimiter),delete e.dataset.date;else{var c=i.container?document.querySelector(i.container):null;c&&(o.container=c),(a=this.inputField=e).classList.add("datepicker-input"),s=w(a.value,o.dateDelimiter)}if(n){var l=n.inputs.indexOf(a),u=n.datepickers;if(l<0||l>1||!Array.isArray(u))throw Error("Invalid rangepicker object.");u[l]=this,Object.defineProperty(this,"rangepicker",{get:function(){return n}})}this.dates=[];var h=$t(this,s);h&&h.length>0&&(this.dates=h),a&&(a.value=Xt(this.dates,o));var p=this.picker=new Ft(this);if(d)this.show();else{var f=Jt.bind(null,this),g=[[a,"keydown",qt.bind(null,this)],[a,"focus",Rt.bind(null,this)],[a,"mousedown",Yt.bind(null,this)],[a,"click",Kt.bind(null,this)],[a,"paste",Ut.bind(null,this)],[document,"mousedown",f],[document,"touchstart",f],[window,"resize",p.place.bind(p)]];X(this,g)}}),[{key:"active",get:function(){return!(!this.picker||!this.picker.active)}},{key:"pickerElement",get:function(){return this.picker?this.picker.element:void 0}},{key:"setOptions",value:function(t){var e=this.picker,i=ft(t,this);Object.assign(this._options,t),Object.assign(this.config,i),e.setOptions(i),Gt(this,3)}},{key:"show",value:function(){if(this.inputField){if(this.inputField.disabled)return;this.inputField!==document.activeElement&&(this._showing=!0,this.inputField.focus(),delete this._showing)}this.picker.show()}},{key:"hide",value:function(){this.inline||(this.picker.hide(),this.picker.update().changeView(this.config.startView).render())}},{key:"destroy",value:function(){return this.hide(),$(this),this.picker.detach(),this.inline||this.inputField.classList.remove("datepicker-input"),delete this.element.datepicker,this}},{key:"getDate",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,i=e?function(i){return R(i,e,t.config.locale)}:function(t){return new Date(t)};return this.config.multidate?this.dates.map(i):this.dates.length>0?i(this.dates[0]):void 0}},{key:"setDate",value:function(){for(var t=arguments.length,e=new Array(t),i=0;i0&&void 0!==arguments[0]?arguments[0]:void 0;if(!this.inline){var e={clear:!0,autohide:!(!t||!t.autohide)},i=w(this.inputField.value,this.config.dateDelimiter);Qt(this,i,e)}}},{key:"refresh",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t&&"string"!=typeof t&&(e=t,t=void 0),Gt(this,"picker"===t?2:"input"===t?1:3,!e)}},{key:"enterEditMode",value:function(){this.inline||!this.picker.active||this.editMode||(this.editMode=!0,this.inputField.classList.add("in-edit","border-blue-700","!border-primary-700"))}},{key:"exitEditMode",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0;if(!this.inline&&this.editMode){var e=Object.assign({update:!1},t);delete this.editMode,this.inputField.classList.remove("in-edit","border-blue-700","!border-primary-700"),e.update&&this.update(e)}}}],[{key:"formatDate",value:function(t,e,i){return R(t,e,i&&tt[i]||tt.en)}},{key:"parseDate",value:function(t,e,i){return q(t,e,i&&tt[i]||tt.en)}},{key:"locales",get:function(){return tt}}])}();function te(t){var e=Object.assign({},t);return delete e.inputs,delete e.allowOneSidedRange,delete e.maxNumberOfDates,e}function ee(t,e,i,n){X(t,[[i,"changeDate",e]]),new Zt(i,n,t)}function ie(t,e){if(!t._updating){t._updating=!0;var i=e.target;if(void 0!==i.datepicker){var n=t.datepickers,r={render:!1},o=t.inputs.indexOf(i),a=0===o?1:0,s=n[o].dates[0],d=n[a].dates[0];void 0!==s&&void 0!==d?0===o&&s>d?(n[0].setDate(d,r),n[1].setDate(s,r)):1===o&&s1&&void 0!==arguments[1]?arguments[1]:{};r(this,t);var n=Array.isArray(i.inputs)?i.inputs:Array.from(e.querySelectorAll("input"));if(!(n.length<2)){e.rangepicker=this,this.element=e,this.inputs=n.slice(0,2),this.allowOneSidedRange=!!i.allowOneSidedRange;var o=ie.bind(null,this),a=te(i),s=[];Object.defineProperty(this,"datepickers",{get:function(){return s}}),ee(this,o,this.inputs[0],a),ee(this,o,this.inputs[1],a),Object.freeze(s),s[0].dates.length>0?ie(this,{target:this.inputs[0]}):s[1].dates.length>0&&ie(this,{target:this.inputs[1]})}}),[{key:"dates",get:function(){return 2===this.datepickers.length?[this.datepickers[0].dates[0],this.datepickers[1].dates[0]]:void 0}},{key:"setOptions",value:function(t){this.allowOneSidedRange=!!t.allowOneSidedRange;var e=te(t);this.datepickers[0].setOptions(e),this.datepickers[1].setOptions(e)}},{key:"destroy",value:function(){this.datepickers[0].destroy(),this.datepickers[1].destroy(),$(this),delete this.element.rangepicker}},{key:"getDates",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0,i=e?function(i){return R(i,e,t.datepickers[0].config.locale)}:function(t){return new Date(t)};return this.dates.map((function(t){return void 0===t?t:i(t)}))}},{key:"setDates",value:function(t,e){var i=h(this.datepickers,2),n=i[0],r=i[1],o=this.dates;this._updating=!0,n.setDate(t),r.setDate(e),delete this._updating,r.dates[0]!==o[1]?ie(this,{target:this.inputs[1]}):n.dates[0]!==o[0]&&ie(this,{target:this.inputs[0]})}}])}();e.DateRangePicker=ne,e.Datepicker=Zt},902:function(t,e,i){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;it._options.maxValue&&(i.value=t._options.maxValue.toString()),null!==t._options.minValue&&parseInt(i.value)=this._options.maxValue||(this._targetEl.value=(this.getCurrentValue()+1).toString(),this._options.onIncrement(this))},t.prototype.decrement=function(){null!==this._options.minValue&&this.getCurrentValue()<=this._options.minValue||(this._targetEl.value=(this.getCurrentValue()-1).toString(),this._options.onDecrement(this))},t.prototype.updateOnIncrement=function(t){this._options.onIncrement=t},t.prototype.updateOnDecrement=function(t){this._options.onDecrement=t},t}();function d(){document.querySelectorAll("[data-input-counter]").forEach((function(t){var e=t.id,i=document.querySelector('[data-input-counter-increment="'+e+'"]'),n=document.querySelector('[data-input-counter-decrement="'+e+'"]'),o=t.getAttribute("data-input-counter-min"),a=t.getAttribute("data-input-counter-max");t?r.default.instanceExists("InputCounter",t.getAttribute("id"))||new s(t,i||null,n||null,{minValue:o?parseInt(o):null,maxValue:a?parseInt(a):null}):console.error('The target element with id "'.concat(e,'" does not exist. Please check the data-input-counter attribute.'))}))}e.initInputCounters=d,"undefined"!=typeof window&&(window.InputCounter=s,window.initInputCounters=d),e.default=s},16:function(t,e,i){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i { + try { + // 2. Dinamički sklapamo URL: npr. "http://localhost:8000/api/operativa/radni-nalozi/" + // Ako sutra promijeniš PUBLIC_API_URL u .env, ovdje se automatski mijenja! + const url = `${API_BASE}${routes.radniNalozi()}`; + + // 3. Koristimo tvoju funkciju getAuthHeaders. + // Pošto joj prosljeđujemo formData, ona NEĆE postaviti 'Content-Type': 'application/json', + // što je ključno da browser sam generira multipart/form-data BOUNDARY! + const serverskiToken = context.cookies.get('access_token')?.value; // ili auth_token ovisno o tvom cookie-ju + const headers = getAuthHeaders(formData, serverskiToken); + + const response = await fetch(url, { + method: 'POST', + body: formData, + headers: headers + }); + + const resData = await response.json(); + + if (response.ok && resData.success) { + return { + success: true, + data: resData.data, + errors: null + }; + } + + return { + success: false, + data: null, + errors: resData.errors || 'Došlo je do pogreške na poslužitelju.' + }; + + } catch (error) { + console.error('[Action Error] Greška pri kreiranju radnog naloga:', error); + return { + success: false, + data: null, + errors: 'Mrežna pogreška ili nedostupan backend.' + }; + } + } + }) +}; \ No newline at end of file diff --git a/001.FRONTEND/src/assets/astro.svg b/001.FRONTEND/src/assets/astro.svg new file mode 100644 index 0000000..8cf8fb0 --- /dev/null +++ b/001.FRONTEND/src/assets/astro.svg @@ -0,0 +1 @@ + diff --git a/001.FRONTEND/src/assets/background.svg b/001.FRONTEND/src/assets/background.svg new file mode 100644 index 0000000..4b2be0a --- /dev/null +++ b/001.FRONTEND/src/assets/background.svg @@ -0,0 +1 @@ + diff --git a/001.FRONTEND/src/components/AkcijePanel.astro b/001.FRONTEND/src/components/AkcijePanel.astro new file mode 100644 index 0000000..7746efc --- /dev/null +++ b/001.FRONTEND/src/components/AkcijePanel.astro @@ -0,0 +1,199 @@ +--- +// src/components/AkcijePanel.astro + +import Button from './Button.astro'; + +interface Props { + tip: 'dashboard' | 'radni-nalog' | 'stroj' | 'vlasnik' | 'vozilo'; // DODANO: vozilo + podaci: any; +} + +const { tip, podaci } = Astro.props; + +// Pomoćna funkcija za formatiranje datuma +const formatDate = (date: string) => + new Date(date).toLocaleDateString('hr-HR', { day: '2-digit', month: '2-digit', year: 'numeric' }); +--- + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/Brojac.jsx b/001.FRONTEND/src/components/Brojac.jsx new file mode 100644 index 0000000..14cfdc8 --- /dev/null +++ b/001.FRONTEND/src/components/Brojac.jsx @@ -0,0 +1,17 @@ +import { useState } from 'preact/hooks'; + +export default function Brojac() { + const [count, setCount] = useState(0); + + return ( +
+

Brojač: {count}

+ +
+ ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/Button.astro b/001.FRONTEND/src/components/Button.astro new file mode 100644 index 0000000..f645397 --- /dev/null +++ b/001.FRONTEND/src/components/Button.astro @@ -0,0 +1,30 @@ +--- +// src/components/Button.astro +interface Props { + type?: "button" | "submit"; + variant?: "primary" | "danger" | "outline"; + id?: string; + class?: string; +} + +const { type = "button", variant = "primary", id, class: className } = Astro.props; + +const variants = { + primary: "bg-blue-600 hover:bg-blue-700 text-white shadow-blue-500/20", + danger: "bg-red-600 hover:bg-red-700 text-white shadow-red-500/20", + outline: "bg-transparent border-2 border-gray-200 text-gray-500 hover:border-blue-600 hover:text-blue-600 shadow-none" +}; +--- + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/Form/LoginForm.jsx b/001.FRONTEND/src/components/Form/LoginForm.jsx new file mode 100644 index 0000000..ee83202 --- /dev/null +++ b/001.FRONTEND/src/components/Form/LoginForm.jsx @@ -0,0 +1,108 @@ +// src/components/auth/Login.jsx +import { h } from 'preact'; +import { useFormSubmit } from '../../hooks/useFormSubmit'; +import { useAuthRedirect } from '../../hooks/useAuthRedirect'; +import { setAuthUser } from '../../stores/appState'; +import { actions } from 'astro:actions'; +import { getTrenutniKorisnik } from '../../lib/api'; +import { loginKorisnika } from '../../stores/appState'; + +export default function LoginFOrm() { + + // Izbaci korisnika ako je već prijavljen + useAuthRedirect('/'); + + // Povezivanje uspješnog logina s NanoStore-om + const handleUspjesnaPrijava = async (backendPodaci) => { + // 1. Dohvaćamo profil s backenda (Race condition osigurač) + const stvarniKorisnik = await getTrenutniKorisnik(backendPodaci.access); + + // Priprema korisničkog objekta (iz baze ili fallback) + const korisnikZaUpis = stvarniKorisnik || backendPodaci.user || { first_name: 'Serviser', role: 'SERVISER' }; + + // 2. 🚀 DRY TRIJUMF: Jedna linija koda koja sprema token i budi AuthStatus gumb! + loginKorisnika(korisnikZaUpis, backendPodaci.access); + }; + + // Konfiguracija kuke za slanje forme + const { handleSubmit, loading } = useFormSubmit( + actions.loginKorisnika, + '/', + "Uspješna prijava u sustav!", + handleUspjesnaPrijava + ); + + return ( +
+ + {/* --- ZAGLAVLJE FORME (Napredni UI) --- */} +
+
+ +
+

Prijava

+

+ ServisLog / Terminal +

+
+ + {/* --- OBRAZAC S POVEZANIM SUBMITOM --- */} +
+ + {/* POLJE: E-MAIL */} +
+ + +
+ + {/* POLJE: LOZINKA */} +
+ + +
+ + {/* REAKTIVNI GUMB SA STILOVIMA I ANIMACIJOM UCITAVANJA */} +
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/Gallery.astro b/001.FRONTEND/src/components/Gallery.astro new file mode 100644 index 0000000..ec84d7f --- /dev/null +++ b/001.FRONTEND/src/components/Gallery.astro @@ -0,0 +1,77 @@ +--- +// src/components/Gallery.astro +import { Image } from 'astro:assets'; +import 'photoswipe/style.css'; + +interface Props { + // Prilagođeno tvom serializeru: 'url' je zapravo 'slika' u JSON-u + images: { + slika: string; + opis?: string; + width?: number; + height?: number; + }[]; + galleryId: string; +} + +const { images = [], galleryId } = Astro.props; + +// Filtriramo važeće slike (pazimo na naziv polja 'slika' iz serializera) +const validImages = images.filter(img => img && img.slika); +--- + +
+ {validImages.map((img) => ( + + {img.opis + + +
+ + {img.opis && {img.opis}} +
+
+ ))} +
+ +{validImages.length === 0 && ( +
+ +

Nema priložene foto dokumentacije.

+
+)} + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/Gallery.jsx b/001.FRONTEND/src/components/Gallery.jsx new file mode 100644 index 0000000..788861f --- /dev/null +++ b/001.FRONTEND/src/components/Gallery.jsx @@ -0,0 +1,47 @@ +// src/components/Gallery.jsx +import { h } from 'preact'; +import { useStore } from '@nanostores/preact'; +import { $offlineStatus } from '../stores/operativaStore'; + +export default function Gallery({ images = [] }) { + const isOffline = useStore($offlineStatus); + + if (images.length === 0) return null; + + return ( +
+ {images.map((img) => { + const originalniUrl = img.slika; + + // Generiramo identičan URL kakav je pohranjen u Cache Storageu + const optimiziraniUrl = `/api/operativa/proxy-image/?url=${encodeURIComponent(originalniUrl)}&w=600`; + + return ( +
+ {/* Slika s on-the-fly optimizacijom i automatskom offline podrškom */} + Dokumentacija kvara { + // Potpuni fallback ako cache izgori ili slika fali, a imamo mrežu - prikaži original + if (!isOffline) e.target.src = originalniUrl; + }} + /> + + {/* Mali vizualni indikator ako radimo u offline načinu rada */} + {isOffline && ( +
+ Offline Cache +
+ )} +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/GenericKarticaItem.astro b/001.FRONTEND/src/components/GenericKarticaItem.astro new file mode 100644 index 0000000..a73853e --- /dev/null +++ b/001.FRONTEND/src/components/GenericKarticaItem.astro @@ -0,0 +1,114 @@ +--- +// src/components/GenericKarticaItem.astro +import { getStatusColorClass } from "../utils/ui"; + +interface Props { + href: string; + status: string; + statusBojaClass?: string; + ikona: string; + naslov: string; + subNaslov?: string; + metaTekst: string; + statusPrikaz: string; + bojaTeme?: "blue" | "indigo" | "emerald" | "red"; // Podrška za teme +} + +const { + href, + status, + statusBojaClass, + ikona, + naslov, + subNaslov, + metaTekst, + statusPrikaz, + bojaTeme = "blue" +} = Astro.props; + +// Mapiranje tema na Tailwind klase +const themes = { + blue: { + hover: "hover:bg-blue-50/30 dark:hover:bg-blue-900/10", + text: "group-hover:text-blue-600 dark:group-hover:text-blue-400", + iconBg: "group-hover:bg-blue-600", + arrow: "group-hover:text-blue-600" + }, + indigo: { + hover: "hover:bg-indigo-50/30 dark:hover:bg-indigo-900/10", + text: "group-hover:text-indigo-600 dark:group-hover:text-indigo-400", + iconBg: "group-hover:bg-indigo-600", + arrow: "group-hover:text-indigo-600" + }, + emerald: { + hover: "hover:bg-emerald-50/30 dark:hover:bg-emerald-900/10", + text: "group-hover:text-emerald-600 dark:group-hover:text-emerald-400", + iconBg: "group-hover:bg-emerald-600", + arrow: "group-hover:text-emerald-400" + } +}; + +const activeTheme = themes[bojaTeme] || themes.blue; +const resolvedStatusColor = statusBojaClass || getStatusColorClass(status); +--- + + +
+ +
+ +
+ +
+
+ + {naslov} + + {subNaslov && ( + + {subNaslov} + + )} +
+ + + {metaTekst} + +
+
+ +
+ + + + + +
+
\ No newline at end of file diff --git a/001.FRONTEND/src/components/Kalendar/Kalendar.astro b/001.FRONTEND/src/components/Kalendar/Kalendar.astro new file mode 100644 index 0000000..4fc923d --- /dev/null +++ b/001.FRONTEND/src/components/Kalendar/Kalendar.astro @@ -0,0 +1,192 @@ +--- +// src/components/Kalendar.astro +import { fetchCalendarEvents } from "../../lib/api"; +import { getStatusColorClass } from "../../utils/ui"; + +// 1. Dohvat podataka s Django API-ja kroz tvoj centralizirani lib +const apiEvents = await fetchCalendarEvents(); + +// 2. Grupiranje događaja po datumu (YYYY-MM-DD) +const eventsByDate = apiEvents.reduce((acc, event) => { + if (!event.start) return acc; + + const dateKey = new Date(event.start).toISOString().split('T')[0]; + if (!acc[dateKey]) acc[dateKey] = []; + + // Koristimo čista, razdvojena polja s novog Django serializera + acc[dateKey].push({ + id: event.id, + title: event.title, + opis: event.opis_cisti, // Čisti opis kvara s backenda + start: event.start, + tip: (event.tip || event.status || 'planirano').toLowerCase(), + radni_nalog: event.radni_nalog || event.id, + je_radni_nalog: event.je_radni_nalog !== undefined ? event.je_radni_nalog : true, + izvrsitelj_ime: event.izvrsitelj_ime, + klijent: event.klijent || null, + stroj: event.stroj || null, + vozilo: event.vozilo_naziv // Čisti naziv vozila s backenda + }); + + return acc; +}, {}); + +// Postavke za trenutni prikaz (Svibanj 2026) +const daysInMonth = 31; +const firstDayOffset = 5; // Petak +const currentYearMonth = "2026-05"; +--- + +
+ +
+ +
+

Svibanj 2026

+ Raspored servisa +
+ +
+ +
+
+
Ned
Pon
Uto
Sri
Čet
Pet
Sub
+
+ +
+ {Array.from({ length: firstDayOffset }).map(() => ( +
+ ))} + + {Array.from({ length: daysInMonth }).map((_, i) => { + const day = i + 1; + const dateKey = `${currentYearMonth}-${day.toString().padStart(2, '0')}`; + const dayEvents = eventsByDate[dateKey] || []; + + return ( +
+ 0 ? 'text-gray-900 dark:text-white' : 'text-gray-300 dark:text-gray-600'} group-hover:text-blue-600`}> + {day.toString().padStart(2, '0')} + + +
+ {dayEvents.map(event => ( +
+ ))} +
+
+ ); + })} + + +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/components/ListaStrojeva.astro b/001.FRONTEND/src/components/ListaStrojeva.astro new file mode 100644 index 0000000..4dc314b --- /dev/null +++ b/001.FRONTEND/src/components/ListaStrojeva.astro @@ -0,0 +1,59 @@ +--- +// src/components/ListaStrojeva.astro +import NaslovList from "./NaslovList.astro"; +import GenericKarticaItem from "./GenericKarticaItem.astro"; +import { getStatusColorClass, formatStatus } from "../utils/ui"; + +interface Props { + strojevi: any[]; + naslov?: string; + prikaziNaslov?: boolean; +} + +const { + strojevi = [], + naslov = "Tehničke jedinice", + prikaziNaslov = true +} = Astro.props; + +// Statistika za NaslovList (npr. koliko ih je aktivno ili na servisu ako imaš te podatke) +const ukupnoStrojeva = strojevi.length; +// Pretpostavljamo da strojevi imaju status, ako nemaju, možemo staviti fiksne labele +const uRadu = strojevi.filter(s => s.status === 'u_radu').length; +--- + +
+ + {prikaziNaslov && ( + + )} + +
+
+ {strojevi.length > 0 ? strojevi.map((s) => ( + + )) : ( +
+ + Nema dostupnih tehničkih jedinica +
+ )} +
+
+
\ No newline at end of file diff --git a/001.FRONTEND/src/components/LoginForm.astro b/001.FRONTEND/src/components/LoginForm.astro new file mode 100644 index 0000000..da29c1b --- /dev/null +++ b/001.FRONTEND/src/components/LoginForm.astro @@ -0,0 +1,110 @@ +--- +// src/components/LoginForm.astro +import Button from "./Button.astro"; +--- + +
+
+
+ +
+

Prijava

+

+ ServisLog / Terminal +

+
+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/components/NaslovList.astro b/001.FRONTEND/src/components/NaslovList.astro new file mode 100644 index 0000000..5d73304 --- /dev/null +++ b/001.FRONTEND/src/components/NaslovList.astro @@ -0,0 +1,193 @@ +--- +// src/components/RadniNalogLista.astro +import NaslovList from "./NaslovList.astro"; + +interface Props { + limit?: number; + naslov?: string; + prikaziNaslov?: boolean; +} + +const { + limit = 0, + naslov = "Zadnje aktivnosti", + prikaziNaslov = true, +} = Astro.props; +--- + +
+ {prikaziNaslov && ( + + )} + +
+ +
+ Sinkronizacija radnih naloga... +
+ +
+
+ Nema zapisa za odabrani status +
+
+
+
+ + + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/Navbar/index.jsx b/001.FRONTEND/src/components/Navbar/index.jsx new file mode 100644 index 0000000..a1e53ae --- /dev/null +++ b/001.FRONTEND/src/components/Navbar/index.jsx @@ -0,0 +1,158 @@ +// src/components/ui/Navbar.jsx +import { h, Fragment } from 'preact'; +import { useState, useEffect, useRef } from 'preact/hooks'; +import { useStore } from '@nanostores/preact'; +import siteConfig from '../../data/site.json'; +import { getBaseUrl } from '../../lib/nav.js'; +import { $currentUser, logoutKorisnika } from '../../stores/appState'; + +export default function Navbar() { + // 1. Reaktivno slušamo stanje ulogiranog mehatroničara/korisnika + const currentUser = useStore($currentUser); + + // 2. Lokalno stanje za otvaranje i zatvaranje mobilnog izbornika + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const menuRef = useRef(null); + const buttonRef = useRef(null); + + // Dohvaćamo trenutnu stazu u pregledniku za aktivne klase (samo na klijentu) + const trenutnaStaza = typeof window !== 'undefined' ? window.location.pathname : ''; + + // Zatvaranje izbornika na klik izvan komponente (Higijena sučelja) + useEffect(() => { + if (!isMenuOpen) return; + + const handleClickOutside = (e) => { + if ( + menuRef.current && !menuRef.current.contains(e.target) && + buttonRef.current && !buttonRef.current.contains(e.target) + ) { + setIsMenuOpen(false); + } + }; + + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }, [isMenuOpen]); + + // Razbijamo naslov na bazi tvog vizualnog stila (prva riječ plava ili naglašena) + const naslovPrvaRijec = siteConfig.title.split(' ')[0]; + const naslovOstatak = siteConfig.title.split(' ').slice(1).join(' '); + + return ( + + ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/Navbar/index_prvi.jsx b/001.FRONTEND/src/components/Navbar/index_prvi.jsx new file mode 100644 index 0000000..944819d --- /dev/null +++ b/001.FRONTEND/src/components/Navbar/index_prvi.jsx @@ -0,0 +1,49 @@ +// src/components/ui/Navbar.jsx +import { h } from 'preact'; +import siteConfig from '../../data/site.json'; +import { getBaseUrl } from '../../lib/nav.js'; + +export default function Navbar() { + // Početna ruta se generira automatski i sigurno prosljeđivanjem korijena + const pocetnaRuta = getBaseUrl('/'); + + // Provjeravamo trenutnu stazu u pregledniku (samo ako smo na klijentu) + const trenutnaStaza = typeof window !== 'undefined' ? window.location.pathname : ''; + + return ( + + ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/Navbar_ref.astro b/001.FRONTEND/src/components/Navbar_ref.astro new file mode 100644 index 0000000..2a2e5f6 --- /dev/null +++ b/001.FRONTEND/src/components/Navbar_ref.astro @@ -0,0 +1,179 @@ +--- +// src/components/Navbar.astro +import site from "../data/site.json"; +import Button from "./Button.astro"; + +const currentPath = Astro.url.pathname; +--- + + + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/PutniNalogLista.astro b/001.FRONTEND/src/components/PutniNalogLista.astro new file mode 100644 index 0000000..2d150da --- /dev/null +++ b/001.FRONTEND/src/components/PutniNalogLista.astro @@ -0,0 +1,161 @@ +--- +// src/components/PutniNalogLista.astro +import NaslovList from "./NaslovList.astro"; + +interface Props { + naslov?: string; + prikaziNaslov?: boolean; + voziloId?: string | number; +} + +const { + naslov = "Putni nalozi i rute", + prikaziNaslov = true, + voziloId = null, +} = Astro.props; +--- + +
+ {prikaziNaslov && ( + + )} + +
+
+ Sinkronizacija putnih naloga... +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/RadniNalogLista.astro b/001.FRONTEND/src/components/RadniNalogLista.astro new file mode 100644 index 0000000..de0f5cb --- /dev/null +++ b/001.FRONTEND/src/components/RadniNalogLista.astro @@ -0,0 +1,202 @@ +--- +// src/components/RadniNalogLista.astro +import NaslovList from "./NaslovList.astro"; + +interface Props { + limit?: number; + naslov?: string; + prikaziNaslov?: boolean; +} + +const { + limit = 0, + naslov = "Zadnje aktivnosti", + prikaziNaslov = true, +} = Astro.props; +--- + +
+ {prikaziNaslov && ( + + )} + +
+ +
+ Sinkronizacija radnih naloga... +
+ +
+
+ Nema zapisa za odabrani status +
+
+
+
+ + + + \ No newline at end of file diff --git a/001.FRONTEND/src/components/ServiserTerminal.astro b/001.FRONTEND/src/components/ServiserTerminal.astro new file mode 100644 index 0000000..b6215fe --- /dev/null +++ b/001.FRONTEND/src/components/ServiserTerminal.astro @@ -0,0 +1,242 @@ +--- +// src/components/ServiserTerminal.astro +import Button from "./Button.astro"; +import Toast from "./Toast.astro"; +--- + +
+ + + + + + + + +
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/components/StatsGrid.astro b/001.FRONTEND/src/components/StatsGrid.astro new file mode 100644 index 0000000..1e1a493 --- /dev/null +++ b/001.FRONTEND/src/components/StatsGrid.astro @@ -0,0 +1,48 @@ +--- +// src/components/StatsGrid.astro +interface Props { + label: string; + value: string | number; + icon: string; + variant?: 'blue' | 'yellow' | 'emerald' | 'gray'; + animate?: boolean; + filterValue?: string; // NOVO: npr. 'u_radu', 'planirano'... + class?: string; +} + +const { + label, + value, + icon, + variant = 'blue', + animate = false, + filterValue = "", + class: className = "" +} = Astro.props; + +const themes = { + blue: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-blue-50 dark:bg-blue-900/30", iconColor: "text-blue-600 dark:text-blue-400", labelColor: "text-gray-400" }, + yellow: { container: "border-yellow-500/20 border-2", iconBg: "bg-yellow-500", iconColor: "text-white", labelColor: "text-yellow-600" }, + emerald: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-emerald-50 dark:bg-emerald-900/30", iconColor: "text-emerald-600 dark:text-emerald-400", labelColor: "text-gray-400" }, + gray: { container: "border-gray-100 dark:border-gray-700", iconBg: "bg-gray-100 dark:bg-gray-800", iconColor: "text-gray-500", labelColor: "text-gray-400" } +}; + +const theme = themes[variant]; +--- + +
+
+ +
+ +
+

+ {value} +

+

+ {label} +

+
+
\ No newline at end of file diff --git a/001.FRONTEND/src/components/ToastContainer.jsx b/001.FRONTEND/src/components/ToastContainer.jsx new file mode 100644 index 0000000..64cc1a8 --- /dev/null +++ b/001.FRONTEND/src/components/ToastContainer.jsx @@ -0,0 +1,67 @@ +// src/components/ui/ToastContainer.jsx +import { h } from 'preact'; +import { useStore } from '@nanostores/preact'; +import { useEffect } from 'preact/hooks'; +import { $toast, ukloniToast } from '../../stores/toastStore'; // Prilagodi putanju tvom toastStoreu + +export default function ToastContainer() { + const toast = useStore($toast); + + // Automatsko zatvaranje toasta nakon 4 sekunde (Higijena sučelja) + useEffect(() => { + if (toast) { + const timer = setTimeout(() => { + ukloniToast(); + }, 3000); + + return () => clearTimeout(timer); + } + }, [toast]); + + if (!toast) return null; + + // Dinamičko dodjeljivanje ikona i boja ovisno o tipu (success / error) + const jeUspjeh = toast.type === 'success'; + + return ( +
+
+ {/* Ikonica s animacijom */} +
+ {jeUspjeh ? ( + + ) : ( + + )} +
+ + {/* Sadržaj obavijesti */} +
+

+ {jeUspjeh ? 'Sustav / Obavijest' : 'Sustav / Greška'} +

+

+ {toast.message} +

+
+ + {/* Gumb za ručno zatvaranje */} + +
+
+ ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/components/WelcomeHeader.astro b/001.FRONTEND/src/components/WelcomeHeader.astro new file mode 100644 index 0000000..8f3791c --- /dev/null +++ b/001.FRONTEND/src/components/WelcomeHeader.astro @@ -0,0 +1,36 @@ +--- +// src/components/WelcomeHeader.astro +import site from "../data/site.json"; + +const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/"; +const currentPageData = site.navigation.find(item => item.url === currentPath); + +console.log("Current Path:", currentPath); + +const { + welcomeHeaderTextH1 = "Dobrodošli", + welcomeHeaderTextH1dodatno = "", + welcomeHeaderPodnaslov = "Pregled sustava", + welcomeHeaderPovratniURL = "", + welcomeHeaderDisplay = true +} = currentPageData || {}; +--- + +{welcomeHeaderDisplay && ( +
+ {welcomeHeaderPovratniURL && ( + + Povratak + + )} + +
+

+ {welcomeHeaderTextH1} {welcomeHeaderTextH1dodatno} +

+

+ {welcomeHeaderPodnaslov} +

+
+
+)} \ No newline at end of file diff --git a/001.FRONTEND/src/components/auth/AuthStatus.jsx b/001.FRONTEND/src/components/auth/AuthStatus.jsx new file mode 100644 index 0000000..64b8028 --- /dev/null +++ b/001.FRONTEND/src/components/auth/AuthStatus.jsx @@ -0,0 +1,32 @@ +// src/components/auth/AuthStatus.jsx +import { h } from 'preact'; +import { useStore } from '@nanostores/preact'; +import { $currentUser, logoutKorisnika } from '../../stores/appState'; // 🚀 Uvozimo i atom i akciju +import Button from '../Button.jsx'; + +export default function AuthStatus() { + const currentUser = useStore($currentUser); + const isLoggedIn = !!currentUser; + + return isLoggedIn ? ( + + ) : ( + + + + ); +} \ No newline at end of file diff --git a/001.FRONTEND/src/data/site.json b/001.FRONTEND/src/data/site.json new file mode 100644 index 0000000..d373dfa --- /dev/null +++ b/001.FRONTEND/src/data/site.json @@ -0,0 +1,107 @@ +{ + "title": "ServisLog", + "description": "Profesionalni sustav za upravljanje servisnim operacijama", + "url": "https://servislog.hr", + "author": "Servis tim", + "theme": { + "mode": "dark", + "color": "blue" + }, + "navigation": [ + { + "name": "Dashboard", + "url": "/", + "icon": "fa-chart-line", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Kontrolna", + "welcomeHeaderTextH1dodatno": " ploča", + "welcomeHeaderPodnaslov": "Pregled ključnih informacija u stvarnom vremenu", + "welcomeHeaderPovratniURL": "" + }, + { + "name": "Radni nalozi", + "url": "/operativa/radni-nalozi", + "icon": "fa-file-signature", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Radni", + "welcomeHeaderTextH1dodatno": " nalozi", + "welcomeHeaderPodnaslov": "Pregled ključnih informacija u stvarnom vremenu", + "welcomeHeaderPovratniURL": "/" + }, + { + "name": "Vozni Park", + "url": "/fleet/vozila", + "icon": "fa-truck-pickup", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Vozni", + "welcomeHeaderTextH1dodatno": " park", + "welcomeHeaderPodnaslov": "Sustavna evidencija i nadzor mobilnih resursa", + "welcomeHeaderPovratniURL": "/" + }, + { + "name": "Registrirani strojevi", + "url": "/fleet/strojevi", + "icon": "fa-truck-pickup", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Vozni", + "welcomeHeaderTextH1dodatno": " park", + "welcomeHeaderPodnaslov": "Sustavna evidencija i nadzor mobilnih resursa", + "welcomeHeaderPovratniURL": "/" + }, + { + "name": "Klijenti", + "url": "/kupci/svi", + "icon": "fa-address-book", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Pregled", + "welcomeHeaderTextH1dodatno": " klijenata", + "welcomeHeaderPodnaslov": "Upravljanje bazom korisnika i partnera", + "welcomeHeaderPovratniURL": "/" + }, + { + "name": "Kalendar", + "url": "/kalendar-dogadaja", + "icon": "fa-calendar-days", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Kalendar", + "welcomeHeaderTextH1dodatno": " događaja", + "welcomeHeaderPodnaslov": "Pregled operacija u stvarnom vremenu", + "welcomeHeaderPovratniURL": "/" + }, + { + "name": "Login", + "url": "/login", + "icon": "fa-calendar-days", + "welcomeHeaderDisplay": false, + "welcomeHeaderTextH1": "Prijava", + "welcomeHeaderTextH1dodatno": " korisnika", + "welcomeHeaderPodnaslov": "Pristup sustavu za ovlaštene korisnike", + "welcomeHeaderPovratniURL": "" + }, + { + "name": "Novi Radni Nalog", + "url": "/operativa/radni-nalozi/novi", + "icon": "fa-calendar-days", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Novi", + "welcomeHeaderTextH1dodatno": " Radni nalog", + "welcomeHeaderPodnaslov": "Otvaranje novog servisnog ili radnog naloga u sustavu", + "welcomeHeaderPovratniURL": "/operativa/radni-nalozi" + }, + { + "name": "Evidencija Servisera", + "url": "/operativa/serviseri", + "icon": "fa-calendar-days", + "welcomeHeaderDisplay": true, + "welcomeHeaderTextH1": "Evidencija", + "welcomeHeaderTextH1dodatno": " servisera", + "welcomeHeaderPodnaslov": "Središnji pregled i administracija terenskih servisnih tehničara", + "welcomeHeaderPovratniURL": "/" + } + ], + "api_endpoints": { + "base": "PUBLIC_API_URL", + "auth": "/token/", + "me": "/users/me/" + } +} \ No newline at end of file diff --git a/001.FRONTEND/src/layouts/Layout.astro b/001.FRONTEND/src/layouts/Layout.astro new file mode 100644 index 0000000..69f172a --- /dev/null +++ b/001.FRONTEND/src/layouts/Layout.astro @@ -0,0 +1,57 @@ +--- +// src/layouts/Layout.astro +import siteConfig from '../data/site.json'; +import { getBaseUrl } from '../lib/nav.js'; +import ToastContainer from '../components/ToastContainer.jsx'; +import AuthStatus from '../components/auth/AuthStatus.jsx'; +import Navbar from '../components/Navbar/index.jsx'; +import { ClientRouter } from 'astro:transitions'; +import "../styles/global.css"; + +interface Props { + title: string; + description?: string; +} + +const { title, description: stranicaDescription } = Astro.props; + +const metaDescription = stranicaDescription || siteConfig.description || "Glavni terminal sustava ServisLog."; +--- + + + + + + + + + + + + + {title} | {siteConfig.title} + + + + + + + + + +
+ +
+ +
+ +
+ +
+ © {new Date().getFullYear()} {siteConfig.title}. Sva prava pridržana. +
+ +
+ + + \ No newline at end of file diff --git a/001.FRONTEND/src/lib/api.js b/001.FRONTEND/src/lib/api.js new file mode 100644 index 0000000..a40ddae --- /dev/null +++ b/001.FRONTEND/src/lib/api.js @@ -0,0 +1,578 @@ +// src/lib/api.js + +// 1. Osiguravamo da API_BASE uvijek završava s točno jednom kosom crtom +const RAW_BASE = import.meta.env.PUBLIC_API_URL; +// Dodajemo "export" kako bi je Astro Actions mogle uvoziti +export const API_BASE = RAW_BASE.endsWith('/') ? RAW_BASE : `${RAW_BASE}/`; + +/** + * Vraća ispravan korijenski URL ovisno o tome izvršava li se kôd na serveru ili klijentu. + */ +export function getBaseApiUrl() { + // 1. Provjeravamo jesmo li u pregledniku (Klijent) + if (typeof window !== 'undefined') { + // Na klijentu puštamo relativni URL kako bi Vite proxy (/api/...) odradio posao + return ''; + } + + // 2. Ako nismo u pregledniku, znači da smo na Astro serveru (SSR unutar Docker-a) + // Koristimo Docker interni naziv servisa 'backend' na portu 8000 + return 'http://backend:8000'; +} + +/** + * POMOĆNA FUNKCIJA: Dohvaća token i postavlja Headere. + * Podržava i klijentski localStorage i serverski (SSR) proslijeđeni token. + * @param {Object|FormData} bodyData - Podaci koji se šalju u body-ju zahtjeva + * @param {string|null} ssrToken - Izborni token proslijeđen sa serverske strane (Astro SSR) + */ +function getAuthHeaders(bodyData = {}, ssrToken = null) { + const headers = {}; + + // 🚀 PAMETAN DOHVAT TOKENA: Ako je proslijeđen ssrToken (server), koristi njega. + // Ako nije, a nalazimo se u pregledniku (klijent), povuci ga iz localStorage. + let token = ssrToken; + if (!token && typeof window !== 'undefined') { + token = localStorage.getItem('access_token'); + } + + if (token) { + headers['Authorization'] = `Bearer ${token}`; // Usklađeno sa Simple JWT standardom + } + + // Provjera je li bodyData FormData (za slike/naloge s terena) + const isFormData = bodyData instanceof FormData; + + // Ako NIJE FormData, šaljemo standardni JSON content type + if (!isFormData) { + headers['Content-Type'] = 'application/json'; + } + + return headers; +} + +/** + * Centralizirana obrada odgovora s Toast podrškom + */ +async function handleResponse(res) { + if (res.status === 401) { + console.warn("Token istekao ili je nevažeći."); + } + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + console.error("API Error Response:", errorData); + + let msg = "Greška pri sinkronizaciji podataka."; + + if (errorData.detail) { + msg = errorData.detail; + } + else if (typeof errorData === 'object' && errorData !== null) { + const kljuceviGresaka = Object.keys(errorData); + + if (kljuceviGresaka.length > 0) { + const prvoPolje = kljuceviGresaka[0]; + const greskaVrijednost = errorData[prvoPolje]; + + if (Array.isArray(greskaVrijednost) && greskaVrijednost.length > 0) { + msg = `${prvoPolje}: ${greskaVrijednost[0]}`; + } else if (typeof greskaVrijednost === 'string') { + msg = `${prvoPolje}: ${greskaVrijednost}`; + } + } + } + + if (typeof window !== 'undefined') { + window.showToast?.(msg, "error"); + } + return null; + } + + // VRAĆA PARSIRAN OBJEKT - Stream je zatvoren nakon ove linije! + return await res.json(); +} + +// --- RUTE --- +export const routes = { + // Autentifikacija + login: () => 'token/', + trenutniKorisnik: () => 'users/me/', + + // Korisnici i operativni tehničari + serviseriSvi: () => 'users/', + serviserTerminal: (id) => `users/${id}/terminal/`, + + // Radni nalozi + radniNalozi: (params = {}) => { + const baseUrl = 'operativa/radni-nalozi/'; + const cleanParams = Object.fromEntries( + Object.entries(params).filter(([_, v]) => v != null) + ); + const queryString = new URLSearchParams(cleanParams).toString(); + return queryString ? `${baseUrl}?${queryString}` : baseUrl; + }, + radniNalogDetalji: (id) => `operativa/radni-nalozi/${id}/`, + sljedeciBrojNaloga: () => 'operativa/radni-nalozi/sljedeci-broj/', + + // Putni nalozi i logistika + putniNalozi: () => 'operativa/putni-nalozi/', + + // Vozila i strojevi (Fleet modul) + vozila: () => 'fleet/vozila/', + strojevi: (vlasnikId = null) => { + const baseUrl = 'fleet/strojevi/'; + if (vlasnikId) return `${baseUrl}?vlasnik=${vlasnikId}`; + return baseUrl; + }, + strojDetalji: (id) => `fleet/strojevi/${id}/`, + + // Kupci / Klijenti + kupciSvi: () => 'kupci/svi/', + kupacDetalji: (id) => `kupci/svi/${id}/`, + + // Kalendar i raspored + mojRaspored: () => 'kalendar/moj-raspored/', + kalendarDogadaji: () => 'kalendar/dogadaji/' +}; + +// --- API METODE --- + +/** + * Dohvat profila korisnika iz opće liste /api/users/ + * @param {string|null} ssrToken - Opcionalni token sa servera (Astro.cookies ili slično) + */ +export async function fetchCurrentUser(ssrToken = null) { + if (typeof window === 'undefined' && !ssrToken) return null; + + try { + const url = `${API_BASE}${routes.trenutniKorisnik()}`; + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders({}, ssrToken) + }); + + const user = await handleResponse(res); + if (user && user.detail) return null; + + return user; + } catch (e) { + console.error("fetchCurrentUser Failure:", e); + return null; + } +} + +/** + * 🚀 NOVO (DRY): Dohvaća sve korisnike i vraća isključivo filtrirane servisere s terena + */ +export async function fetchServiseri() { + try { + const url = `${API_BASE}${routes.serviseriSvi()}`; + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + const korisnici = await handleResponse(res); + if (!korisnici) return []; + + const lista = Array.isArray(korisnici) ? korisnici : (korisnici.results || []); + + // Filtriranje na temelju polja 'uloga' iz tvog CustomUser modela + return lista.filter(u => String(u.uloga).toUpperCase().trim() === 'SERVISER'); + } catch (e) { + console.error("fetchServiseri Failure:", e); + return []; + } +} + +/** + * 🚀 NOVO (DRY): Dohvaća ugniježđene terminal podatke za specifičnog servisera (Nalozi, Putni, Vozila) + * @param {number|string} id - ID servisera čiji se terminal učitava + */ +export async function fetchServiserTerminalData(id) { + try { + const url = `${API_BASE}${routes.serviserTerminal(id)}`; + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + return await handleResponse(res); + } catch (e) { + console.error(`fetchServiserTerminalData za ID ${id} neuspješan:`, e); + return null; + } +} + +/** + * Dohvat i filtriranje radnih naloga (za tablice i liste u operativi) + */ +export async function fetchRadniNalozi(params = {}) { + try { + // Logika čišćenja i slaganja query parametara je sada delegirana routes objektu! + const url = `${API_BASE}${routes.radniNalozi(params)}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + return await handleResponse(res); + } catch (e) { + console.error("fetchRadniNalozi Failure:", e); + return []; + } +} + +/** + * Paralelni dohvat podataka za glavno dispečersko sučelje (Dashboard flote i naloga) + */ +export async function fetchDashboardData(params = {}) { + try { + // Dinamički povlačimo staze iz routes objekta za paralelno okidanje + const urlVozila = `${API_BASE}${routes.vozila()}`; + const urlNalozi = `${API_BASE}${routes.radniNalozi()}`; + + const [resV, resN] = await Promise.all([ + fetch(urlVozila, { method: 'GET', headers: getAuthHeaders() }), + fetch(urlNalozi, { method: 'GET', headers: getAuthHeaders() }) + ]); + + // Budući da Dashboard podatke često renderiraš u paralelnim karticama, + // koristimo brzi i sigurni fallback u slučaju prazne baze ili neočekivanog formata + const vozilaData = resV.ok ? await resV.json().catch(() => []) : []; + const naloziData = resN.ok ? await resN.json().catch(() => []) : []; + + return { + vozila: Array.isArray(vozilaData) ? vozilaData : (vozilaData.results || []), + nalozi: Array.isArray(naloziData) ? naloziData : (naloziData.results || []) + }; + } catch (e) { + console.error("FetchDashboardData Failure:", e); + return { vozila: [], nalozi: [] }; + } +} + +/** + * Prijava korisnika i pohrana JWT tokena + */ +export async function login(email, password) { + try { + // Povlačenje centralizirane rute za token + const url = `${API_BASE}${routes.login()}`; + + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }); + + const data = await res.json(); + if (!res.ok) throw new Error(data.detail || "Neuspješna prijava"); + + localStorage.setItem('access_token', data.access); + localStorage.setItem('refresh_token', data.refresh); + return { success: true }; + } catch (e) { + console.error("Login Error:", e); + return { success: false, error: e.message }; + } +} + +/** + * Odjava korisnika i čišćenje lokalne pohrane + */ +export function logout() { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + if (typeof window !== 'undefined') window.location.href = '/login'; +} + +/** + * Dohvat detalja pojedinačnog kupca/klijenta + */ +export async function fetchKupacDetalji(id) { + try { + const url = `${API_BASE}${routes.kupacDetalji(id)}`; + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + return await handleResponse(res); + } catch (e) { + console.error(`Greška u fetchKupacDetalji za ID ${id}:`, e); + return null; + } +} + +/** + * Unos novog servisnog vozila u bazu flote + */ +export async function createVozilo(payload) { + try { + const url = `${API_BASE}${routes.vozila()}`; + const res = await fetch(url, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify(payload) + }); + + const data = await handleResponse(res); + if (data && typeof window !== 'undefined') { + window.showToast?.("Vozilo uspješno uneseno!", "success"); + } + return data; + } catch (e) { + console.error("Greška u createVozilo:", e); + return null; + } +} + +/** + * Kreiranje novog radnog naloga (šalje se FormData zbog učitavanja slika s terena) + */ +export async function createNalog(formData) { + try { + // Koristimo bazičnu rutu bez parametara za POST zahtjev + const url = `${API_BASE}${routes.radniNalozi()}`; + const res = await fetch(url, { + method: 'POST', + headers: getAuthHeaders(formData), // Automatski izbacuje Content-Type za FormData + body: formData + }); + return await handleResponse(res); + } catch (e) { + console.error("Greška u createNalog:", e); + return null; + } +} + +/** + * Parcijalno ažuriranje radnog naloga (npr. promjena statusa, dodavanje opisa) preko centralizirane rute + */ +export async function patchNalog(id, data) { + try { + // Koristimo novu stazu za detalje naloga iz routes objekta + const url = `${API_BASE}${routes.radniNalogDetalji(id)}`; + + const res = await fetch(url, { + method: 'PATCH', + headers: getAuthHeaders(), + body: JSON.stringify(data) + }); + + return await handleResponse(res); + } catch (e) { + console.error(`Greška u patchNalog za ID ${id}:`, e); + return null; + } +} + +/** + * Dohvat svih kalendarskih događaja (planirani servisi, atesti, tereni) + */ +export async function fetchCalendarEvents() { + try { + // Koristimo novu stazu iz routes objekta + const url = `${API_BASE}${routes.kalendarDogadaji()}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + // Centralizirana obrada odgovora s Toast error handlingom + const data = await handleResponse(res); + + if (!data) return []; // Siguran fallback ako zahtjev baci grešku + + return Array.isArray(data) ? data : (data.results || []); + } catch (e) { + console.error("fetchCalendarEvents Failure:", e); + return []; + } +} + +/** + * Dohvat osobnog rasporeda servisera iz kalendara + */ +export async function fetchMojRaspored() { + try { + // Koristimo stazu iz routes objekta + const url = `${API_BASE}${routes.mojRaspored()}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + // Centralizirana obrada odgovora s Toast error handlingom + const data = await handleResponse(res); + + if (!data) return []; // Siguran fallback na prazan niz ako zahtjev ne prođe + + return Array.isArray(data) ? data : (data.results || []); + } catch (e) { + console.error("fetchMojRaspored Failure:", e); + return []; + } +} + +/** + * Dohvat popisa svih kupaca/klijenata za dropdown u novom nalogu + */ +export async function fetchKupciData() { + try { + // Koristimo stazu iz routes objekta + const url = `${API_BASE}${routes.kupciSvi()}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + // Centralizirana obrada odgovora + const data = await handleResponse(res); + + if (!data) return { kupci: [] }; // Fallback ako je token nevažeći + + // Vraćamo objekt u formatu koji novi.astro destrukturira na serveru + return { + kupci: Array.isArray(data) ? data : (data.results || []) + }; + } catch (e) { + console.error("fetchKupciData Failure:", e); + return { kupci: [] }; + } +} + +/** + * Dohvat popisa strojeva (opcionalno filtrirano po vlasniku) pomoću centralizirane rute + */ +export async function fetchStrojeviData(vlasnikId = null) { + try { + // Generiramo ispravan URL preko routes objekta + const url = `${API_BASE}${routes.strojevi(vlasnikId)}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + // Koristimo centraliziranu obradu odgovora (s Toast podrškom) + const data = await handleResponse(res); + + if (!data) return { strojevi: [] }; // Siguran fallback ako je zahtjev prekinut (401, 500...) + + // Vraćamo objekt sa strojevima prateći strukturu koju novi.astro očekuje (.map destructuring) + return { + strojevi: Array.isArray(data) ? data : (data.results || []) + }; + } catch (e) { + console.error("fetchStrojeviData Failure:", e); + return { strojevi: [] }; + } +} + +/** + * Dohvat popisa svih putnih naloga pomoću centralizirane rute i handleResponse-a + */ +export async function fetchPutniNaloziData() { + try { + // Koristimo zajedničku stazu iz routes objekta + const url = `${API_BASE}${routes.putniNalozi()}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() + }); + + // Koristimo centraliziranu obradu odgovora (s Toast error handlingom) + const data = await handleResponse(res); + + if (!data) return []; // Fallback ako je handleResponse presreo grešku i vratio null + + // Provjera vraća li Django čistu listu ili paginirani rezultatski objekt (results) + return Array.isArray(data) ? data : (data.results || []); + } catch (e) { + console.error("Greška pri dohvatu putnih naloga:", e); + return []; + } +} + +/** + * Dohvaća sljedeći slobodni broj radnog naloga s backenda + * Osigurano protiv duplih kosih crta i preflight redirecta + */ +export async function fetchSljedeciBrojNaloga() { + try { + // Pametno spajanje baze i rute (isto kao u ostalim očišćenim metodama) + const base = API_BASE.endsWith('/') ? API_BASE : `${API_BASE}/`; + const ruta = routes.sljedeciBrojNaloga(); + const url = `${base}${ruta}`; + + const res = await fetch(url, { + method: 'GET', + headers: getAuthHeaders() // Koristi centralizirane headere umjesto sirovog objekta + }); + + if (res.ok) { + return await res.json(); + } else { + console.error("Greška na backendu pri dohvaćanju brojača:", res.status); + return { broj_naloga: "RN-2026-XXXX" }; + } + } catch (err) { + console.error("Mrežna greška u fetchSljedeciBrojNaloga:", err); + return { broj_naloga: "RN-2026-XXXX" }; + } +} + +/** + * Kreiranje novog putnog naloga - S koso crtom (trailing slash) za Django usklađenost + */ +export async function createPutniNalog(radniNalogId, voziloId) { + try { + // Koristimo novu stazu iz routes objekta + const url = `${API_BASE}${routes.putniNalozi()}`; + + const res = await fetch(url, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + radni_nalog_id: parseInt(radniNalogId, 10), + vozilo: parseInt(voziloId, 10) + }) + }); + + // Vraća gotov JSON objekt (ili null ako je toast okinuo grešku) + return await handleResponse(res); + } catch (e) { + console.error("createPutniNalog krah:", e); + return null; + } +} + + +// NOVO (ZADRŽI) + +/** + * Dohvaća podatke o trenutno ulogiranom korisniku iz Django baze podataka. + * Podržava prosljeđivanje eksplicitnog tokena radi sprječavanja race conditiona. + */ +export async function getTrenutniKorisnik(eksplicitanToken = null, params = {}) { + const url = getApiUrl('auth.me', params); + if (!url) return null; + + const options = { + method: 'GET', + // 🚀 GENIJALNO USKLAĐIVANJE: Proslijeđeni token guramo ravno kao drugi parametar tvoje funkcije! + // Tvoj getAuthHeaders će ga prepoznati kao primarni token i potpuno preskočiti sessionStorage. + headers: getAuthHeaders(null, eksplicitanToken) + }; + + const res = await fetch(url, options); + const ishod = await handleResponse(res, { url, options }); + + return ishod || null; +} \ No newline at end of file diff --git a/001.FRONTEND/src/lib/nav.js b/001.FRONTEND/src/lib/nav.js new file mode 100644 index 0000000..48d3ee2 --- /dev/null +++ b/001.FRONTEND/src/lib/nav.js @@ -0,0 +1,18 @@ +// src/lib/nav.js + +/** + * Pretvara relativnu stazu iz JSON-a u siguran i dinamički URL. + * Ako Astro ima konfiguriran base URL (npr. u cloudu), ova funkcija se brine o tome. + * @param {string} url - Sirovi URL iz konfiguracije (npr. "/operativa/radni-nalozi") + * @returns {string} + */ +export function getBaseUrl(url) { + if (!url) return '/'; + + // Astro automatski osigurava import.meta.env.BASE_URL ovisno o okruženju + const baseUrl = import.meta.env.BASE_URL || '/'; + + // Čistimo duple kose crte (slashes) radi urednosti routera + const spojeniUrl = `${baseUrl}/${url}`.replace(/\/+/g, '/'); + return spojeniUrl; +} \ No newline at end of file diff --git a/001.FRONTEND/src/pages/fleet/strojevi/[id].astro b/001.FRONTEND/src/pages/fleet/strojevi/[id].astro new file mode 100644 index 0000000..56cd45b --- /dev/null +++ b/001.FRONTEND/src/pages/fleet/strojevi/[id].astro @@ -0,0 +1,163 @@ +--- +// src/pages/fleet/strojevi/[id].astro +import Layout from "../../../layouts/Layout.astro"; +import Button from "../../../components/Button.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; + +// Importiranje utilitija +const { id } = Astro.params; +const API_BASE = import.meta.env.PUBLIC_API_URL; + +let stroj = null; + +try { + // Dohvaćamo točno određeni stroj + const res = await fetch(`${API_BASE}/fleet/strojevi/${id}/?t=${Date.now()}`); + if (res.ok) stroj = await res.json(); +} catch (e) { + console.error("Greška pri dohvatu detalja stroja:", e); +} + +if (!stroj) return Astro.redirect("/404"); + +// Logika za Atest status (npr. ako je unutar 30 dana ili prošao) +const atestDate = stroj.datum_zadnjeg_atesta ? new Date(stroj.datum_zadnjeg_atesta) : null; +const isAtestExpired = atestDate && atestDate < new Date(); +--- + + +
+ +
+
+ + Povratak u park + +
+

+ {stroj.naziv} +

+ +
+

+ Vlasnik: {stroj.vlasnik_naziv} +

+
+ +
+
+ Serijski broj + {stroj.serijski_broj} +
+
+ +
+
+ +
+
+ + Godište + {stroj.godina_proizvodnje || 'N/A'} +
+ +
+ + Radni sati +
+ {parseFloat(stroj.radni_sati).toLocaleString('hr-HR')} + H +
+
+ +
+ + Zadnji atest + + {stroj.datum_zadnjeg_atesta ? new Date(stroj.datum_zadnjeg_atesta).toLocaleDateString('hr-HR') : 'Nema podataka'} + +
+ +
+ + Proizvođač + {stroj.marka} +
+
+ +
+ +
+
+
+
+
+ +
+
+ Model + {stroj.model_stroja} +
+
+ Tip jedinice + {stroj.tip_human_readable} +
+
+ Registracija + {stroj.registracija || 'NIJE REGISTRIRAN'} +
+
+
+ +
+ +
+
+ +
+
+
+ {stroj.naziv} +
+
+
+
+ +
+

Status održavanja

+

+ Ovaj stroj ({stroj.naziv}) je zadnji put atestiran {stroj.datum_zadnjeg_atesta}. Prema servisnom planu, toranjske dizalice zahtijevaju periodični pregled svakih 12 mjeseci. Trenutni radni sati ({stroj.radni_sati} h) sugeriraju potrebu za redovnim podmazivanjem sklopova. +

+
+
+ +
+ + + +
+ +
+
+
\ No newline at end of file diff --git a/001.FRONTEND/src/pages/fleet/strojevi/index.astro b/001.FRONTEND/src/pages/fleet/strojevi/index.astro new file mode 100644 index 0000000..631c43b --- /dev/null +++ b/001.FRONTEND/src/pages/fleet/strojevi/index.astro @@ -0,0 +1,101 @@ +--- +// src/pages/fleet/strojevi/index.astro +import Layout from "../../../layouts/Layout.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; +import NaslovList from "../../../components/NaslovList.astro"; +import GenericKarticaItem from "../../../components/GenericKarticaItem.astro"; + +// Importiranje utilitija +import { fetchStrojeviData, fetchCurrentUser } from "../../../lib/api"; +import { getStatusColorClass, formatStatus } from "../../../utils/ui"; + +// 1. DOHVAT FILTERA IZ URL-a +const url = new URL(Astro.request.url); +const vlasnikId = url.searchParams.get('vlasnik'); + +// 2. DOHVAT PODATAKA (Paralelno) +const [{ strojevi }, user] = await Promise.all([ + fetchStrojeviData(vlasnikId), + fetchCurrentUser() +]); + +// 3. LOGIKA ZA DINAMIČKE INFORMACIJE +const imeKupca = strojevi.length > 0 && vlasnikId ? strojevi[0].vlasnik_naziv : null; +const imeKorisnika = user?.first_name || "Serviser"; +const ulogaKorisnika = user?.is_serviser ? "Tehnička Služba" : "Logistika"; + +let dinamickiPodnaslov = "Pregled kompletne strojne flote i dizalica"; +if (vlasnikId && imeKupca) { + dinamickiPodnaslov = `Prikaz strojeva dodijeljenih klijentu: ${imeKupca}`; +} +--- + + +
+ + + + +
+ +
+ + +
+ + + {vlasnikId && ( + + )} +
+ + +
+
+ {strojevi.length > 0 ? strojevi.map((s) => ( + + )) : ( +
+ +

Nema strojeva u bazi podataka

+
+ )} +
+
+
+ + +
+ +
+ +
+
+
\ No newline at end of file diff --git a/001.FRONTEND/src/pages/fleet/vozila/[id].astro b/001.FRONTEND/src/pages/fleet/vozila/[id].astro new file mode 100644 index 0000000..03621b3 --- /dev/null +++ b/001.FRONTEND/src/pages/fleet/vozila/[id].astro @@ -0,0 +1,228 @@ +// src/pages/fleet/vozila/[id].astro +--- +import Layout from "../../../layouts/Layout.astro"; +import Button from "../../../components/Button.astro"; // Uvoz tvoje zajedničke komponente za gumbe +import { getStatusColorClass } from "../../../utils/ui"; + +// 1. Dohvat ID-ja iz URL parametara +const { id } = Astro.params; + +let vozilo = null; +let radniNalozi = []; + +// 2. Dohvat podataka s Django API-ja (Backend) +try { + const voziloResponse = await fetch(`http://127.0.0.1:8000/api/fleet/vozila/${id}/`); + if (voziloResponse.ok) { + vozilo = await voziloResponse.json(); + } + + const naloziResponse = await fetch(`http://127.0.0.1:8000/api/operativa/radni-nalozi/`); + if (naloziResponse.ok) { + const sviNalozi = await naloziResponse.json(); + radniNalozi = sviNalozi.filter((nalog) => nalog.vozilo === parseInt(id) || nalog.vozilo_id === parseInt(id)); + } +} catch (error) { + console.error("Greška pri dohvaćanju podataka s Django API-ja:", error); +} + +if (!vozilo) { + return Astro.redirect("/fleet/vozila?error=not-found"); +} + +function formatStatusLocal(status) { + const map = { + 'aktivan': 'Aktivan', + 'servis': 'Na servisu', + 'neaktivan': 'Izvan pogona' + }; + return map[status?.toLowerCase()] || status; +} + +function getPrioritetClass(prioritet) { + const p = prioritet?.toLowerCase(); + if (p === 'hitan' || p === 'visok') return 'bg-red-500/10 text-red-500 border-red-500/20'; + if (p === 'srednji') return 'bg-amber-500/10 text-amber-500 border-amber-500/20'; + return 'bg-blue-500/10 text-blue-500 border-blue-500/20'; +} + +const statusKlasa = getStatusColorClass(vozilo.status?.toLowerCase()); +--- + + +
+ +
+ +
+ +
+ +
+
+
+ +
+
+

+ {vozilo.naziv} +

+ + Interni ID: #{vozilo.id} + +
+
+ + + {vozilo.status_prikaz || formatStatusLocal(vozilo.status)} + +
+ +
+
+ + Registracijska oznaka + +
+ + + {vozilo.registracija} + +
+
+ +
+ + Trenutna kilometraža + +
+ + + {vozilo.trenutni_kilometri?.toLocaleString('hr-HR') || 0} KM + +
+
+ +
+
+ +
+ + Trenutni raspored / Baza + +

+ Terenska Baza +

+

+ Vozilo je mapirano na centralni logistički sustav fleet managementa. Sve izmjene kilometara i servisnih naloga sinkroniziraju se u realnom vremenu s radnim nalozima operativnog tima. +

+
+
+ +
+ + +
+
+ + +
+
+
+ +

Povijest Radnih Naloga

+
+ + Ukupno: {radniNalozi.length} + +
+ + {radniNalozi.length === 0 ? ( +
+ +

Nema evidentiranih radnih naloga za ovo vozilo.

+
+ ) : ( +
+ {radniNalozi.map((nalog) => ( +
+
+
+ #{nalog.broj_naloga || nalog.id} +
+
+

+ {nalog.opis_kvara || nalog.naslov || "Opis radova nije definiran"} +

+
+ + + {nalog.datum_otvaranja ? new Date(nalog.datum_otvaranja).toLocaleDateString('hr-HR') : "Nepoznat datum"} + + {nalog.kilometraža_prijave && ( + + + {nalog.kilometraža_prijave.toLocaleString('hr-HR')} KM + + )} +
+
+
+ +
+ + {nalog.prioritet || "Normalno"} + + + +
+
+ ))} +
+ )} +
+ +
+
+ + diff --git a/001.FRONTEND/src/pages/fleet/vozila/index.astro b/001.FRONTEND/src/pages/fleet/vozila/index.astro new file mode 100644 index 0000000..bd17ced --- /dev/null +++ b/001.FRONTEND/src/pages/fleet/vozila/index.astro @@ -0,0 +1,107 @@ +--- +// src/pages/fleet/vozila/index.astro +import Layout from "../../../layouts/Layout.astro"; +import Button from "../../../components/Button.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; +import NaslovList from "../../../components/NaslovList.astro"; +import GenericKarticaItem from "../../../components/GenericKarticaItem.astro"; + +// Importiranje utilitija i centraliziranog API-ja +import { fetchDashboardData, fetchCurrentUser } from "../../../lib/api"; +import { getStatusColorClass, formatStatus } from "../../../utils/ui"; + +// 1. DOHVAT FILTERA IZ URL-a (npr. ?status=servis) +const statusFilter = Astro.url.searchParams.get('status')?.toLowerCase(); + +// 2. DOHVAT PODATAKA +const [dashboardData, user] = await Promise.all([ + fetchDashboardData(), + fetchCurrentUser() +]); + +const vozila = dashboardData?.vozila || []; + +// 3. LOGIKA STATISTIKE (Uvijek se računa iz originalnog niza, neovisno o filteru) +const aktivnaVozila = vozila.filter(v => v.status?.toLowerCase() === 'aktivan').length; +const naServisu = vozila.filter(v => v.status?.toLowerCase() === 'servis').length; +const neaktivnaVozila = vozila.filter(v => v.status?.toLowerCase() === 'neaktivan').length; + +// 4. LOGIKA FILTRIRANJA (Serverska strana) +let filtriraniVozila = vozila; +if (statusFilter) { + filtriraniVozila = vozila.filter(v => v.status?.toLowerCase() === statusFilter); +} + +// Možeš dodati limit kroz props ako ovu stranicu ikada budeš koristio kao parcijalnu komponentu +const limit = 0; +const prikazanaVozila = limit > 0 ? filtriraniVozila.slice(0, limit) : filtriraniVozila; + +// Dinamički naslov liste ovisno o odabranom filteru flote +const prikazaniNaslov = statusFilter + ? `Flota: ${formatStatus(statusFilter)}` + : "Aktivna Flota"; +--- + + +
+ + + +
+ +
+ + +
+
+ {prikazanaVozila.length > 0 ? prikazanaVozila.map((v) => ( + + )) : ( +
+ +

Nema vozila za odabrani status

+ {!statusFilter && ( + + )} +
+ )} +
+
+
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/index.astro b/001.FRONTEND/src/pages/index.astro new file mode 100644 index 0000000..a11a037 --- /dev/null +++ b/001.FRONTEND/src/pages/index.astro @@ -0,0 +1,100 @@ +--- +// src/pages/index.astro +import Layout from "../layouts/Layout.astro"; +import WelcomeHeader from "../components/WelcomeHeader.astro"; +import AkcijePanel from "../components/AkcijePanel.astro"; +import RadniNalogLista from "../components/RadniNalogLista.astro"; +import StatsGrid from "../components/StatsGrid.astro"; +import Kalendar from "../components/Kalendar/Kalendar.astro"; +import Brojac from '../components/Brojac.jsx'; + +// API i Centralizirani Utils iz api.js +import { fetchDashboardData } from "../lib/api"; + +// 1. Dohvat podataka s backenda (Samo bazični dashboard podaci koji se mogu poslužiti asinkrono) +const data = await fetchDashboardData(); + +const nalozi = data?.nalozi || []; + +// 2. Kalkulacija statistike tolerantna na velika/mala slova (.toLowerCase()) +const naloziURadu = nalozi.filter(n => n.status?.toLowerCase() === 'u_radu').length; +const planiraniNalozi = nalozi.filter(n => n.status?.toLowerCase() === 'planirano').length; + +const zavrseniNalozi = nalozi.filter(n => { + const statusMalo = n.status?.toLowerCase(); + return statusMalo === 'zavrseno' || statusMalo === 'naplaceno'; +}).length; +--- + + +
+ + + + + +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/kalendar-dogadaja.astro b/001.FRONTEND/src/pages/kalendar-dogadaja.astro new file mode 100644 index 0000000..99f89b0 --- /dev/null +++ b/001.FRONTEND/src/pages/kalendar-dogadaja.astro @@ -0,0 +1,33 @@ +--- +// src/pages/kalendar-dogadaja.astro +import Layout from "../layouts/Layout.astro"; +import WelcomeHeader from "../components/WelcomeHeader.astro"; +import Kalendar from "../components/Kalendar/Kalendar.astro"; +--- + + +
+ + + +
+ +
+ +
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/kupci/[id].astro b/001.FRONTEND/src/pages/kupci/[id].astro new file mode 100644 index 0000000..f9f73cf --- /dev/null +++ b/001.FRONTEND/src/pages/kupci/[id].astro @@ -0,0 +1,131 @@ +--- +// src/pages/kupci/[id].astro +import Layout from "../../layouts/Layout.astro"; +import AkcijePanel from "../../components/AkcijePanel.astro"; +import ListaStrojeva from "../../components/ListaStrojeva.astro"; +import { fetchStrojeviData, fetchRadniNalozi, fetchKupacDetalji, routes } from "../../lib/api"; + +const { id } = Astro.params; + +// 1. Paralelno dohvaćanje podataka (brže renderiranje) +// Koristimo status 'u_radu' jer smo utvrdili da Django ne prepoznaje 'otvoreno' +const [strojeviData, kupac, nalozi] = await Promise.all([ + fetchStrojeviData(id), + fetchKupacDetalji(id), + fetchRadniNalozi({ klijent: id, status: 'u_radu' }) +]); + +const strojevi = strojeviData?.strojevi || []; + +// 2. Sigurnosni redirect ako kupac ne postoji +if (!kupac) return Astro.redirect("/404"); +--- + + +
+ + +
+
+ + Povratak klijentima + +
+

+ {kupac.naziv} +

+ +
+

+ Glavno sjedište: {kupac.grad}, {kupac.adresa} +

+
+ + +
+
+ Porezni broj + OIB: {kupac.oib} +
+
+ +
+
+ + +
+
+ + Strojni Park +
+ {strojevi.length} + Jedinica +
+
+ + + + + + Otvoreni radni Nalozi + +
+ + {String(nalozi?.length || 0)} + + +
+
+
+ + +
+
+
+ +
+ +
+
+

Napredna analitika voznog parka

+

Pristupite detaljnim izvještajima i povijesti strojeva.

+
+ + + +
+
+ + +
+ + + +
+ +

Naplata i Ugovori

+
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/001.FRONTEND/src/pages/kupci/svi.astro b/001.FRONTEND/src/pages/kupci/svi.astro new file mode 100644 index 0000000..a6d5e32 --- /dev/null +++ b/001.FRONTEND/src/pages/kupci/svi.astro @@ -0,0 +1,97 @@ +--- +// src/pages/kupci/svi.astro +import Layout from "../../layouts/Layout.astro"; +import AkcijePanel from "../../components/AkcijePanel.astro"; +import WelcomeHeader from "../../components/WelcomeHeader.astro"; + +// Importiranje nove funkcije +import { fetchKupciData } from "../../lib/api"; +import { getStatusColorClass } from "../../utils/ui"; + +// 1. DOHVAT PODATAKA (Koristimo novu funkciju) +const { kupci } = await fetchKupciData(); + +// 2. LOGIKA STATISTIKE +const aktivniKupci = kupci.filter(k => k.aktivno === true).length; +const brojPravnih = kupci.filter(k => k.tip === 'pravno').length; +--- + + +
+ + + +
+ +
+
+

Popis klijenata

+
+ Aktivni: {aktivniKupci} + Tvrtke: {brojPravnih} +
+
+ + +
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/login.astro b/001.FRONTEND/src/pages/login.astro new file mode 100644 index 0000000..d0d3198 --- /dev/null +++ b/001.FRONTEND/src/pages/login.astro @@ -0,0 +1,18 @@ +--- +// src/pages/login.astro +import Layout from "../layouts/Layout.astro"; +import LoginForm from "../components/Form/LoginForm.jsx"; +import WelcomeHeader from "../components/WelcomeHeader.astro"; +--- + + +
+ + + + + + + +
+
\ No newline at end of file diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro new file mode 100644 index 0000000..3f488fd --- /dev/null +++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/[id].astro @@ -0,0 +1,293 @@ +--- +// src/pages/operativa/radni-nalozi/[id].astro +import Layout from "../../../layouts/Layout.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; +import Gallery from "../../../components/Gallery.astro"; + +// Utiliti za vizualni prikaz +import { getStatusColorClass } from "../../../utils/ui"; + +const { id } = Astro.params; +const API_BASE = import.meta.env.PUBLIC_API_URL; + +let nalog = null; +let dostupnaVozila = []; + +try { + // 1. Dohvaćamo detaljne podatke o radnom nalogu (RadniNalogDetaljiSerializer) + const resNalog = await fetch(`${API_BASE}/operativa/radni-nalozi/${id}/?t=${Date.now()}`); + if (resNalog.ok) nalog = await resNalog.json(); + + // 2. Ako radni nalog nema vezan putni nalog, dohvaćamo popis vozila za dropdown u klijentskom otoku + if (nalog && !nalog.putni_nalog) { + const resVozila = await fetch(`${API_BASE}/fleet/vozila/`); + if (resVozila.ok) dostupnaVozila = await resVozila.json(); + } +} catch (e) { + console.error("Greška pri dohvatu podataka s API-ja:", e); +} + +if (!nalog) return Astro.redirect("/404"); + +// Priprema datuma za prikaz +const datumKreiranja = new Date(nalog.datum_kreiranja).toLocaleDateString('hr-HR', { + day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' +}); +--- + + +
+ +
+
+ + Povratak na dashboard + +

+ Radni nalog #{nalog.broj_naloga} +

+
+

+ Otvoreno: {datumKreiranja}h +

+ +

+ {nalog.izvrsitelj_ime || 'Nedodijeljeno'} +

+
+
+ +
+
+ + {nalog.status_display || nalog.status} + +
+
+ +
+ +
+ +
+ +

+ {nalog.opis_kvara} +

+
+ +
+
+ + +

{nalog.stroj?.naziv}

+ + SN: {nalog.stroj?.serijski_broj || 'Nema SN'} + +
+

Radni sati: {nalog.stroj?.radni_sati || 0} h

+

Lokacija: {nalog.stroj?.lokacija || 'Teren'}

+
+
+ +
+ +

{nalog.klijent?.naziv}

+

{nalog.klijent?.grad} OIB: {nalog.klijent?.oib}

+ + +
+
+ + {nalog.vozilo ? ( +
+ + +

{nalog.vozilo?.naziv}

+ + Registracija: {nalog.vozilo?.registracija || ''} + +
+

Trenutni km: {nalog.vozilo.trenutni_kilometri?.toLocaleString() || '0'} km

+

Status vozila: {nalog.vozilo?.status_prikaz || 'Aktivno na terenu'}

+
+
+ ) : ( +
+ +

Radni nalog bez aktivnog putnog naloga / vozila

+
+ )} + +
+
+ + + {nalog.slike?.length || 0} SLIKA + +
+ + +
+ +
+ +
+ + +
+

Logistika i Terenski Put

+ + {nalog.putni_nalog ? ( +
+

+ Povezan putni nalog #{nalog.putni_nalog.broj_naloga} +

+
+

Polazna kilometraža: {nalog.putni_nalog.pocetna_km?.toLocaleString()} km

+

Status puta: {nalog.putni_nalog.status}

+
+ + Otvori putni nalog + +
+ ) : ( +
+

+ Ovaj radni nalog trenutno nema vezan putni nalog. Za potrebe obrade dnevnica odaberite vozilo: +

+ + + + +
+ )} +
+ +
+

Zadnja aktivnost

+
+
+ +
+
+

Ažurirano

+

+ {new Date(nalog.datum_azuriranja).toLocaleDateString('hr-HR')} u {new Date(nalog.datum_azuriranja).toLocaleTimeString('hr-HR', {hour:'2-digit', minute:'2-digit'})}h +

+
+
+
+ +
+

Sistemski ID: {nalog.id}

+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro new file mode 100644 index 0000000..1e0331a --- /dev/null +++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/index.astro @@ -0,0 +1,66 @@ +--- +// src/pages/operativa/radni-nalozi/index.astro +import Layout from "../../../layouts/Layout.astro"; +import Button from "../../../components/Button.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; +import RadniNalogLista from "../../../components/RadniNalogLista.astro"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; +import NaslovList from "../../../components/NaslovList.astro"; + +// 1. DOHVAT PODATAKA +import { fetchRadniNalozi, fetchCurrentUser } from "../../../lib/api"; +import { formatStatus, getStatusColorClass } from "../../../utils/ui"; +import site from "../../../data/site.json"; + +// Paralelno dohvaćamo naloge i podatke o trenutnom korisniku +const klijentId = Astro.url.searchParams.get('klijent'); +const statusParam = Astro.url.searchParams.get('status'); + +const [nalozi, user] = await Promise.all([ + fetchRadniNalozi({ klijent: klijentId, status: statusParam }), + fetchCurrentUser() +]); + +// 2. LOGIKA ZA HEADER I BROJAČE +const uRadu = nalozi.filter(n => n.status === 'u_radu').length; +const planirano = nalozi.filter(n => n.status === 'planirano').length; +const siteWelcomeHeader = site.navigation.find(({name}) => name === "Kalendar"); + +// Podaci za WelcomeHeader +const imeKorisnika = user?.first_name || "Serviser"; +--- + + +
+ + + + +
+ +
+ + + + +
+ + +
+ +
+ +
+
+ +
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro b/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro new file mode 100644 index 0000000..c3f56cd --- /dev/null +++ b/001.FRONTEND/src/pages/operativa/radni-nalozi/novi.astro @@ -0,0 +1,232 @@ +--- +// src/pages/operativa/radni-nalozi/novi.astro +import Layout from "../../../layouts/Layout.astro"; +import Button from "../../../components/Button.astro"; +import { fetchKupciData, fetchStrojeviData } from "../../../lib/api"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; + +// 1. DOHVAT PODATAKA NA SERVERU +const { kupci } = await fetchKupciData(); +const { strojevi } = await fetchStrojeviData(); +--- + + +
+ + + +
+
+ +
+ + AUTO-GENERIRANO +
+

* Broj će biti dodijeljen nakon spremanja

+
+ +
+ +
+ + {new Date().toLocaleDateString('hr-HR')} +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+ -- Prvo odaberi klijenta -- + +
+ + + + +
+
+
+ +
+
+ + +
+ +
+
+ + +
+ +
+ +
+ + +
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/operativa/serviseri/[id].astro b/001.FRONTEND/src/pages/operativa/serviseri/[id].astro new file mode 100644 index 0000000..3582e5a --- /dev/null +++ b/001.FRONTEND/src/pages/operativa/serviseri/[id].astro @@ -0,0 +1,51 @@ +--- +// src/pages/operativa/serviseri/[id].astro +import Layout from "../../../layouts/Layout.astro"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; +import RadniNalogLista from "../../../components/RadniNalogLista.astro"; +import AkcijePanel from "../../../components/AkcijePanel.astro"; + +// 🚀 Hvatamo ID servisera iz same putanje URL-a +const { id } = Astro.params; + +// 🎯 POPRAVAK: Uklonjen serverski fetchCurrentUser i redirect jer se token nalazi na klijentu! +--- + + +
+ + + +
+ +
+ + {/* Univerzalna CSR lista koja sama povlači naloge preko api.js ovisno o ulogi */} + + +
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/pages/operativa/serviseri/index.astro b/001.FRONTEND/src/pages/operativa/serviseri/index.astro new file mode 100644 index 0000000..2070294 --- /dev/null +++ b/001.FRONTEND/src/pages/operativa/serviseri/index.astro @@ -0,0 +1,136 @@ +--- +// src/pages/operativa/serviseri/index.astro +import Layout from "../../../layouts/Layout.astro"; +import WelcomeHeader from "../../../components/WelcomeHeader.astro"; +import RadniNalogLista from "../../../components/RadniNalogLista.astro"; +--- + + +
+ + + +
+ + + + + +
+
+
+ + \ No newline at end of file diff --git a/001.FRONTEND/src/scripts/filters.js b/001.FRONTEND/src/scripts/filters.js new file mode 100644 index 0000000..dfef65f --- /dev/null +++ b/001.FRONTEND/src/scripts/filters.js @@ -0,0 +1,70 @@ +// // src/scripts/filters.js + +// Upravlja sa item.style.display na osnovu aktivnog filtera i data-status atributa +// Također upravlja sa stilovima kartica (ring, border) i naslovom filtera +// export function initFilters() { +// const cards = document.querySelectorAll('.stat-card'); +// const items = document.querySelectorAll('.nalog-item'); +// const title = document.getElementById('filter-title'); +// let activeFilter = null; + +// cards.forEach(card => { +// card.addEventListener('click', () => { +// const filter = card.getAttribute('data-filter'); + +// if (activeFilter === filter) { +// activeFilter = null; +// items.forEach(item => item.style.display = 'flex'); +// cards.forEach(c => c.classList.remove('ring-4', 'ring-blue-600/20', 'border-blue-600')); +// if (title) title.innerText = "Zadnje aktivnosti"; +// } else { +// activeFilter = filter; +// cards.forEach(c => c.classList.remove('ring-4', 'ring-blue-600/20', 'border-blue-600')); +// card.classList.add('ring-4', 'ring-blue-600/20', 'border-blue-600'); + +// if (title) title.innerText = `Filtrirano: ${filter.replace('_', ' ')}`; + +// items.forEach(item => { +// const status = item.getAttribute('data-status'); +// item.style.display = (status === filter) ? 'flex' : 'none'; +// }); +// } +// }); +// }); +// } + + + +// src/scripts/filters.js + +export function initFilters() { + const cards = document.querySelectorAll('.stat-card'); + + // Dohvaćamo trenutni aktivni filter iz URL-a + const urlParams = new URLSearchParams(window.location.search); + const currentActiveFilter = urlParams.get('status'); + + cards.forEach(card => { + const filterValue = card.getAttribute('data-filter'); + + // Dodaj vizualno aktivno stanje ako se URL podudara s gumbom + if (currentActiveFilter === filterValue) { + card.classList.add('ring-4', 'ring-blue-600/20', 'border-blue-600'); + } + + card.addEventListener('click', () => { + const newParams = new URLSearchParams(window.location.search); + + if (currentActiveFilter === filterValue) { + // Ako kliknemo na već aktivni filter, poništavamo ga + newParams.delete('status'); + } else { + // Inače postavljamo novi filter + newParams.set('status', filterValue); + } + + // Osvježi stranicu s novim parametrom - to okida novi API request na serveru + window.location.search = newParams.toString(); + }); + }); +} \ No newline at end of file diff --git a/001.FRONTEND/src/stores/appState.js b/001.FRONTEND/src/stores/appState.js new file mode 100644 index 0000000..9652b94 --- /dev/null +++ b/001.FRONTEND/src/stores/appState.js @@ -0,0 +1,40 @@ +// src/stores/appState.js +import { atom } from 'nanostores'; +import { resetAllStores } from './rootStore'; // Centralni reset svih ostalih store-ova + +// 1. Jedinstveni izvor istine za cijelu aplikaciju +export const $currentUser = atom(null); + +/** + * DRY AKCIJA: Kompletna prijava korisnika u sustav + * @param {Object} stvarniKorisnik - Profilni podaci s Django backenda + * @param {string} token - JWT access token + */ +export function loginKorisnika(stvarniKorisnik, token) { + if (typeof window !== 'undefined') { + sessionStorage.setItem('access_token', token); + } + // Upisujemo korisnika u atom - sve komponente (uključujući AuthStatus) se instantno ažuriraju + $currentUser.set(stvarniKorisnik); +} + +/** + * DRY AKCIJA: Potpuna i sigurna odjava iz sustava + */ +export function logoutKorisnika() { + // 1. Čistimo token s jednog mjesta + if (typeof window !== 'undefined') { + sessionStorage.removeItem('access_token'); + } + + // 2. Pokrećemo tvoj centralni reset koji čisti sva ostala stanja (naloge, vozila...) + resetAllStores(); + + // 3. Postavljamo i samog korisnika na null + $currentUser.set(null); + + // 4. Preusmjeravamo na login + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } +} \ No newline at end of file diff --git a/001.FRONTEND/src/stores/fleetStore.js b/001.FRONTEND/src/stores/fleetStore.js new file mode 100644 index 0000000..2413dbc --- /dev/null +++ b/001.FRONTEND/src/stores/fleetStore.js @@ -0,0 +1,24 @@ +// src/stores/fleetStore.js +import { atom } from 'nanostores'; +import { isClient, cacheImagesForOffline } from '../utils/cacheHelper'; + +// 1. Stanja specifična za Vozni Park +export const $voziloDetalji = atom(null); +export const $offlineStatus = atom(isClient ? !navigator.onLine : false); + +if (isClient) { + window.addEventListener('online', () => $offlineStatus.set(false)); + window.addEventListener('offline', () => $offlineStatus.set(true)); +} + +// 2. Akcija koja koristi isti predložak +export async function setVoziloDetalji(vozilo) { + $voziloDetalji.set(vozilo); + + if (!vozilo || !vozilo.slike_vozila) return; + + const stazeSlika = vozilo.slike_vozila.map(s => s.datoteka); + + // 🚀 Isti helper, drugi spremnik (npr. fleet-slike-cache) + await cacheImagesForOffline(stazeSlika, 'fleet-slike-cache', 600); +} \ No newline at end of file diff --git a/001.FRONTEND/src/stores/galleryStore.js b/001.FRONTEND/src/stores/galleryStore.js new file mode 100644 index 0000000..1f857de --- /dev/null +++ b/001.FRONTEND/src/stores/galleryStore.js @@ -0,0 +1,10 @@ +// galleryStore.js +import { atom } from 'nanostores'; + +// Početno stanje je prazan niz +export const $galleryImages = atom([]); + +// Funkcija za ažuriranje (možeš je pozvati iz bilo koje komponente) +export const updateGallery = (newImages) => { + $galleryImages.set(newImages); +}; \ No newline at end of file diff --git a/001.FRONTEND/src/stores/kalendarStore.js b/001.FRONTEND/src/stores/kalendarStore.js new file mode 100644 index 0000000..0e174aa --- /dev/null +++ b/001.FRONTEND/src/stores/kalendarStore.js @@ -0,0 +1,8 @@ +// src/stores/kalendarStore.js +import { atom } from 'nanostores'; + +export const mojRaspored = atom([]); +export const kalendarDogadaji = atom([]); + +export const setMojRaspored = (data) => mojRaspored.set(data); +export const setKalendarDogadaji = (data) => kalendarDogadaji.set(data); \ No newline at end of file diff --git a/001.FRONTEND/src/stores/kupciStore.js b/001.FRONTEND/src/stores/kupciStore.js new file mode 100644 index 0000000..167b15d --- /dev/null +++ b/001.FRONTEND/src/stores/kupciStore.js @@ -0,0 +1,6 @@ +// src/stores/kupciStore.js +import { atom } from 'nanostores'; + +export const kupci = atom([]); + +export const setKupci = (data) => kupci.set(data); \ No newline at end of file diff --git a/001.FRONTEND/src/stores/operativaStore.js b/001.FRONTEND/src/stores/operativaStore.js new file mode 100644 index 0000000..e264339 --- /dev/null +++ b/001.FRONTEND/src/stores/operativaStore.js @@ -0,0 +1,36 @@ +// src/stores/operativaStore.js +import { atom } from 'nanostores'; +import { isClient, cacheImagesForOffline } from '../utils/cacheHelper'; + +// 1. Globalna stanja modula +export const $radniNalogDetalji = atom(null); +export const $offlineStatus = atom(isClient ? !navigator.onLine : false); + +// 2. Slušač mrežnog statusa (izvršava se samo na klijentu) +if (isClient) { + window.addEventListener('online', () => $offlineStatus.set(false)); + window.addEventListener('offline', () => $offlineStatus.set(true)); +} + +/** + * Funkcija za postavljanje podataka u trgovinu uz automatsku offline sinkronizaciju slika + * @param {Object} nalog - Objekt radnog naloga s backenda + */ +export async function setNalogDetalji(nalog) { + // Instantno punimo store tekstualnim podacima (brzi UI rendering) + $radniNalogDetalji.set(nalog); + + // Ako nalog nema slika, prekidamo daljnji proces keširanja + if (!nalog || !nalog.slike || nalog.slike.length === 0) return; + + // Izvlačimo samo čiste URL-ove slika iz objekata + const stazeSlika = nalog.slike.map(s => s.slika); + + // 🚀 KORISTIMO UTILS TEMPLATE: Pokrećemo izoliranu i sigurnu optimizaciju + await cacheImagesForOffline(stazeSlika, 'operativa-slike-cache', 600); +} + +// 3. Opcionalno: Pomoćna akcija za čišćenje stanja kod navigacije +export function ocistiNalogDetalji() { + $radniNalogDetalji.set(null); +} \ No newline at end of file diff --git a/001.FRONTEND/src/stores/rootStore.js b/001.FRONTEND/src/stores/rootStore.js new file mode 100644 index 0000000..e1c264d --- /dev/null +++ b/001.FRONTEND/src/stores/rootStore.js @@ -0,0 +1,42 @@ +// src/stores/rootStore.js +import { radniNalozi, putniNalozi, serviseri, activeNalogId } from './operativaStore'; +import { vozila, strojevi } from './fleetStore'; +import { kupci } from './kupciStore'; +import { mojRaspored, kalendarDogadaji } from './kalendarStore'; +import { $galleryImages } from './galleryStore'; +import { $toasts } from './toastStore'; + +// 🚀 KLJUČNI DODATAK: Uvozimo autentifikacijska stanja iz appState-a +import { $currentUser, activeUserRole } from './appState'; + +/** + * Resetira apsolutno sva stanja unutar aplikacije. + * Koristi se primarno prilikom odjave korisnika (Logout) kako bi se spriječilo + * curenje podataka u memoriji preglednika između različitih sesija. + */ +export function resetAllStores() { + // --- Čišćenje operative --- + radniNalozi.set([]); + putniNalozi.set([]); + serviseri.set([]); + activeNalogId.set(null); + + // --- Čišćenje flote i klijenata --- + vozila.set([]); + strojevi.set([]); + kupci.set([]); + + // --- Čišćenje kalendara i rasporeda --- + mojRaspored.set([]); + kalendarDogadaji.set([]); + + // --- Čišćenje sučelja (galerije i obavijesti) --- + $galleryImages.set([]); + $toasts.set([]); + + // --- 🚀 RESTART AUTENTIFIKACIJSKIH ATOMA --- + $currentUser.set(null); + activeUserRole.set('GOST'); + + console.log("Sustav ServisLog je uspješno resetiran: svi podaci u memoriji su očišćeni."); +} \ No newline at end of file diff --git a/001.FRONTEND/src/stores/toastStore.js b/001.FRONTEND/src/stores/toastStore.js new file mode 100644 index 0000000..a3794da --- /dev/null +++ b/001.FRONTEND/src/stores/toastStore.js @@ -0,0 +1,25 @@ +// src/stores/toastStore.js +import { atom } from 'nanostores'; + +// 1. Inicijalno stanje trgovine (početno je null jer nema aktivne obavijesti) +export const $toast = atom(null); + +/** + * 🚀 Centralna funkcija za ispaljivanje obavijesti kroz ERP sustav. + * @param {string} message - Tekstualna poruka koja se prikazuje serviseru + * @param {'success' | 'error'} type - Tip obavijesti (zelena ili crvena) + */ +export function showToast(message, type = 'success') { + $toast.set({ + message, + type, + id: Date.now() // Jedinstveni vremenski pečat (korisno za re-okidanje istih poruka) + }); +} + +/** + * 🚀 Funkcija za ručno ili automatsko čišćenje obavijesti s ekrana (Higijena sučelja) + */ +export function ukloniToast() { + $toast.set(null); +} \ No newline at end of file diff --git a/001.FRONTEND/src/styles/global.css b/001.FRONTEND/src/styles/global.css new file mode 100644 index 0000000..d0f65cb --- /dev/null +++ b/001.FRONTEND/src/styles/global.css @@ -0,0 +1,67 @@ +/* 1. Uvezi Tailwind v4 */ +@import "tailwindcss"; + +/* 2. Reci Tailwindu da skenira Flowbite datoteke unutar node_modules (zamjena za staro 'content' polje) */ +@source "../../node_modules/flowbite/**/*.js"; + +/* 3. Ako koristiš Flowbite-ov CSS plugin, u v4 se on unosi ovako: */ +@plugin "flowbite/plugin"; + +@font-face { + font-family: 'Inter'; + src: url('/webfonts/inter/inter-400.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; /* 🚀 KLJUČNO: Preglednik odmah prikazuje fallback font dok se Inter ne učita */ +} + +@font-face { + font-family: 'Inter'; + src: url('/webfonts/inter/inter-500.woff2') format('woff2'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/webfonts/inter/inter-600.woff2') format('woff2'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/webfonts/inter/inter-700.woff2') format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/webfonts/inter/inter-800.woff2') format('woff2'); + font-weight: 800; + font-style: normal; + font-display: swap; +} + +body { + font-family: 'Inter', sans-serif; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(1rem); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in-up { + animation: fadeInUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} \ No newline at end of file diff --git a/001.FRONTEND/src/utils/auth.js b/001.FRONTEND/src/utils/auth.js new file mode 100644 index 0000000..24fe86a --- /dev/null +++ b/001.FRONTEND/src/utils/auth.js @@ -0,0 +1,61 @@ +// src/utils/auth.js +import { fetchCurrentUser } from "../lib/api"; + +/** + * Pomoćna funkcija za dohvat sigurne rute (Single Source of Truth) + */ +export function dohvatiRutuPoUlozi(uloga, userId = '') { + const ulogaGore = uloga ? String(uloga).toUpperCase().trim() : ''; + if (ulogaGore === 'SERVISER') { + return userId ? `/operativa/serviseri/${userId}` : '/operativa/serviseri/'; + } + return '/'; +} + +/** + * Jednostavan klijentski guard koji samo provjerava valjanost sesije i ovlasti + * @param {string[]} dozvoljeneUloge - Niz uloga koje smiju vidjeti stranicu + */ +export async function provjeriPristupStranici(dozvoljeneUloge = []) { + if (typeof window === 'undefined') return; + + const token = localStorage.getItem('access_token'); + + // 1. Ako uopće nema tokena, vrati ga na prijavu + if (!token) { + if (window.location.pathname !== '/login') { + window.location.href = '/login'; + } + return; + } + + try { + // 2. Dohvaćamo profil trenutnog korisnika s backenda + const user = await fetchCurrentUser(); + + // 3. Ako je token nevažeći/istekao, počisti local pohranu i izbaci ga van + if (!user || !user.uloga) { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + window.location.href = '/login'; + return; + } + + const korisnikUlogaGore = String(user.uloga).toUpperCase().trim(); + + // 4. Provjera eksplicitnih ovlasti (Samo ako ih je stranica zatražila) + if (dozvoljeneUloge.length > 0) { + const dozvoljeneUlogeGore = dozvoljeneUloge.map(u => String(u).toUpperCase().trim()); + + // Ako korisnik nema ulogu koja je navedena u dozvoljenima za tu stranicu + if (!dozvoljeneUlogeGore.includes(korisnikUlogaGore)) { + // Saznaj kamo pripada s obzirom na ulogu i preusmjeri ga + window.location.href = dohvatiRutuPoUlozi(korisnikUlogaGore, user.id); + return; + } + } + + } catch (err) { + console.error("Kritičan krah u provjeri pristupa:", err); + } +} \ No newline at end of file diff --git a/001.FRONTEND/src/utils/cacheHelper.js b/001.FRONTEND/src/utils/cacheHelper.js new file mode 100644 index 0000000..b9d098b --- /dev/null +++ b/001.FRONTEND/src/utils/cacheHelper.js @@ -0,0 +1,50 @@ +// src/utils/cacheHelper.js + +/** + * Provjerava je li aplikacija trenutno u pregledniku (klijent) + */ +export const isClient = typeof window !== 'undefined'; + +/** + * Generira siguran, optimiziran proxy URL za sliku na temelju zadane širine + * @param {string} originalUrl - Izvorna staza slike (npr. /media/slika.jpg) + * @param {number} width - Željena širina za on-the-fly kompresiju + * @returns {string} + */ +export function getOptimizedImageUrl(originalUrl, width = 600) { + if (!originalUrl) return ''; + // Ako je URL već proxy ili eksterni, vrati ga, inače ga omotaj + if (originalUrl.includes('proxy-image')) return originalUrl; + return `/api/operativa/proxy-image/?url=${encodeURIComponent(originalUrl)}&w=${width}`; +} + +/** + * Asinkrano preuzima i pohranjuje slike u lokalni Browser Cache Storage za offline rad + * @param {Array} urlArray - Niz originalnih URL-ova slika + * @param {string} cacheName - Naziv cache spremnika (npr. 'operativa-cache') + * @param {number} width - Širina na kojoj se slike keširaju + */ +export async function cacheImagesForOffline(urlArray, cacheName = 'global-media-cache', width = 600) { + if (!isClient || !urlArray || urlArray.length === 0) return; + + try { + const cache = await caches.open(cacheName); + + for (const originalUrl of urlArray) { + if (!originalUrl) continue; + + const optimiziraniUrl = getOptimizedImageUrl(originalUrl, width); + const vecKesirano = await cache.match(optimiziraniUrl); + + if (!vecKesirano) { + console.log(`[Offline Cache] Keširam za izvanmrežni rad: ${optimiziraniUrl}`); + // Fetch prolazi kroz Django Proxy, povlači WebP i sprema ga u lokalnu memoriju + await cache.add(optimiziraniUrl).catch(err => + console.warn(`[Offline Cache] Neuspješan fetch za: ${optimiziraniUrl}`, err) + ); + } + } + } catch (err) { + console.error("[Offline Cache Krah]:", err); + } +} \ No newline at end of file diff --git a/001.FRONTEND/src/utils/ui.js b/001.FRONTEND/src/utils/ui.js new file mode 100644 index 0000000..ef72139 --- /dev/null +++ b/001.FRONTEND/src/utils/ui.js @@ -0,0 +1,111 @@ +// src/utils/ui.js + +/** + * Vraća set Tailwind klasa za specifičnu temu + * @param {string} tema - 'indigo', 'blue', 'emerald', 'amber', 'red' + */ +export function getThemeClasses(tema = "indigo") { + const themes = { + indigo: { + bg: "bg-indigo-50", + bgDark: "dark:bg-indigo-900/20", + text: "text-indigo-600", + textDark: "dark:text-indigo-400", + border: "border-indigo-100", + borderDark: "dark:border-indigo-900/30", + shadow: "shadow-indigo-500/5", + hover: "hover:bg-indigo-50/30 dark:hover:bg-indigo-900/10", + icon: "group-hover:bg-indigo-600 group-hover:text-white" + }, + blue: { + bg: "bg-blue-50", + bgDark: "dark:bg-blue-900/20", + text: "text-blue-600", + textDark: "dark:text-blue-400", + border: "border-blue-100", + borderDark: "dark:border-blue-900/30", + shadow: "shadow-blue-500/5", + hover: "hover:bg-blue-50/30 dark:hover:bg-blue-900/10", + icon: "group-hover:bg-blue-600 group-hover:text-white" + }, + emerald: { + bg: "bg-emerald-50", + bgDark: "dark:bg-emerald-900/20", + text: "text-emerald-600", + textDark: "dark:text-emerald-400", + border: "border-emerald-100", + borderDark: "dark:border-emerald-900/30", + shadow: "shadow-emerald-500/5", + hover: "hover:bg-emerald-50/30 dark:hover:bg-emerald-900/10", + icon: "group-hover:bg-emerald-600 group-hover:text-white" + }, + amber: { + bg: "bg-amber-50", + bgDark: "dark:bg-amber-900/20", + text: "text-amber-600", + textDark: "dark:text-amber-400", + border: "border-amber-100", + borderDark: "dark:border-amber-900/30", + shadow: "shadow-amber-500/5", + hover: "hover:bg-amber-50/30 dark:hover:bg-amber-900/10", + icon: "group-hover:bg-amber-600 group-hover:text-white" + }, + red: { + bg: "bg-red-50", + bgDark: "dark:bg-red-900/20", + text: "text-red-600", + textDark: "dark:text-red-400", + border: "border-red-100", + borderDark: "dark:border-red-900/30", + shadow: "shadow-red-500/5", + hover: "hover:bg-red-50/30 dark:hover:bg-red-900/10", + icon: "group-hover:bg-red-600 group-hover:text-white" + } + }; + + return themes[tema] || themes.indigo; +} + +/** + * Određuje boju kružića i animaciju na temelju statusa (Vozila + Radni Nalozi) + */ +export function getStatusColorClass(status) { + // 1. Provjera postoji li status. Ako ne, vrati sivu boju. + if (!status) return 'bg-gray-400'; + + // 2. Siguran poziv toLowerCase() + const s = String(status).toLowerCase(); + + switch (s) { + case 'u_radu': return 'bg-yellow-500 animate-pulse'; + case 'planirano': + case 'aktivan': return 'bg-blue-500'; + case 'zavrseno': return 'bg-emerald-500'; + case 'naplaceno': return 'bg-gray-500 opacity-50'; + case 'servis': + case 'hitno': return 'bg-red-500 animate-pulse'; + default: return 'bg-gray-400'; + } +} + +/** + * Pretvara tehnički status u ljudima čitljiv format + * Dodana provjera da spriječi toLowerCase error + */ +export function formatStatus(status) { + // Provjera ako je status prazan + if (!status) return 'Nepoznato'; + + const labels = { + 'planirano': 'Planirano', + 'u_radu': 'U radu', + 'zavrseno': 'Završeno', + 'naplaceno': 'Naplaćeno', + 'aktivan': 'Aktivan', + 'servis': 'Na servisu', + 'hitno': 'HITNO' + }; + + const s = String(status).toLowerCase(); + return labels[s] || s; +} \ No newline at end of file diff --git a/001.FRONTEND/tsconfig.json b/001.FRONTEND/tsconfig.json new file mode 100644 index 0000000..3832a3d --- /dev/null +++ b/001.FRONTEND/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [ + ".astro/types.d.ts", + "**/*" + ], + "exclude": [ + "dist" + ], + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a10b653 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2026 bleese14 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5cf49b --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# 003-fleet-management + +Razvoj aplikacije (003) + +Aplikacija za servisera - Izrada radnih naloga, putnih naloga, kalendar, GPS + +## Migracije + +```bash +# migracije: users, kupci, fleet, operations, kalendar +python manage.py makemigrations +python manage.py migrate +``` + +## Populate DB + +```bash +python mange.py populate_kupci 14 +python mange.py populate_stroj 14 +python mange.py populate_vozila 4 +python mange.py populate_radninalog 10 +``` + +## Docker + diff --git a/biljeske/LOCALSTORAGE_UI_OBAVIJESTI.MD b/biljeske/LOCALSTORAGE_UI_OBAVIJESTI.MD new file mode 100644 index 0000000..9a8d5f9 --- /dev/null +++ b/biljeske/LOCALSTORAGE_UI_OBAVIJESTI.MD @@ -0,0 +1,400 @@ +# Upit + +```md +Obrazloži kao da radiš prezentaciju kako je implementirano rješenje preko localStorage + +Obrazloži kako se mijenja stanje u UI iz loading u loaded + +Obrazloži što je potrebno na django rest frameworku da ovo funkcionira +``` + + +# Odgovor (Gemini) + +Evo izravne inženjerske prezentacije i analize arhitekture sustava, fokusirane **isključivo** na priložene datoteke (`api.js`, `index.astro` i `RadniNalogLista.astro`), s jasnim obrazloženjem svih traženih mehanizama i DRF preduvjeta. + +--- + +## 1. Implementacija autentifikacije i autorizacije preko `localStorage`-a + +U priloženom kodu, upravljanje sesijom i dokazivanje identiteta prema Django backendu u potpunosti se oslanja na klijentski `localStorage`. + +``` +[Klijent: login()] ───> POST /token/ ───> [Django Simple JWT] + │ + (access & refresh) <───┘ + │ + localStorage.setItem('access_token', data.access) + │ +[Klijent: getAuthHeaders()] ──┴──> Headers: { Authorization: Bearer } + +``` + +### Detaljna mehanika unutar priloženih datoteka: + +* +**Pohrana pri prijavi:** Unutar `api.js`, funkcija `login(email, password)` šalje POST zahtjev na endpoint definiran u `routes.login()`. Kada Django odobri pristup, asinkroni kôd presreće odgovor i upisuje tokene u memoriju preglednika: + + +```javascript +localStorage.setItem('access_token', data.access); +localStorage.setItem('refresh_token', data.refresh); + +``` + + +* +**Presretanje i slaganje zaglavlja (Interceptors):** Funkcija `getAuthHeaders(bodyData)` služi kao centralni generator sigurnosnih metapodataka. Ona prvo provjerava nalazi li se kôd u kontekstu preglednika kako bi sigurno pristupila `localStorage`-u bez rušenja Node.js okruženja: + + +```javascript +const token = typeof window !== 'undefined' ? localStorage.getItem('access_token') : null; + +``` + + +Ako token postoji, on se ubacuje u standardni format Simple JWT-a: `headers['Authorization'] = 'Bearer ' + token`. + + +* +**Automatsko čišćenje:** Funkcija `logout()` u `api.js` rješava suprotan proces – briše ključeve `access_token` i `refresh_token` iz `localStorage`-a te preusmjerava korisnika na stranicu za prijavu. + + + +--- + +## 2. Tranzicija stanja u UI-ju iz *Loading* u *Loaded* + +Budući da su priložene datoteke `index.astro` i `RadniNalogLista.astro` u ovom trenutku konfigurirane kao **serverske komponente (SSR)** , tranzicija stanja iz *Loading* u *Loaded* odvija se na razini samog poslužitelja (Node.js/Docker) prije nego što HTML uopće stigne do preglednika. + +### Korak po korak: Kako se mijenja stanje unutar priloženog koda + +1. **Asinkroni paralelni dohvat (Podaci na čekanju / "Loading"):** +Na samom vrhu `index.astro`, unutar frontmattera (`---`), pokreće se paralelni dohvat podataka s baze pomoću `Promise.all`. U tom milisekundnom prozoru, dok Node.js čeka odgovor s Django API-ja, aplikacija je u "Loading" stanju na razini poslužitelja: + + +```javascript +const [data, user] = await Promise.all([ fetchDashboardData(), fetchCurrentUser() ]); + +``` + + +2. **Obrada i punjenje strukture (Data Hydration / "Loaded"):** +Čim se `Promise.all` razriješi, stanje prelazi u "Loaded" na serveru. Podaci se destrukturiraju unutar `index.astro` i `RadniNalogLista.astro`. Istog trenutka, asinkrone varijable popunjavaju statička mjesta u kodu: + + +* +`user?.first_name` definira pozdrav u `WelcomeHeader` komponenti. + + +* +`nalozi.filter(...)` proračunava točne brojke za `StatsGrid` kartice (Planirano, U radu, Gotovo). + + + + +3. **Isporuka čistog HTML-a klijentu:** +Komponenta `RadniNalogLista.astro` prima podatke iz `fetchDashboardData()`. Ako nalozi postoje, `.map()` funkcija generira njezinu strukturu i iscrtava gotove `GenericKarticaItem` komponente s točnim brojevima i stanjima. Korisnik na ekranu ne vidi nikakvo učitavanje niti kosture, već mu se stranica otvara trenutno napunjena podacima. + + + +--- + +## 3. Što je potrebno na Django REST Frameworku da ovo funkcionira + +Pregledom poslanih klijentskih metoda unutar `api.js`, tvoj Django backend mora striktno podržavati sljedeće DRF značajke: + +### A. Konfiguracija Simple JWT Autentifikacije + +Unutar `api.js`, funkcija `getAuthHeaders()` injektira `Authorization: Bearer `. Da bi DRF prepoznao ovaj potpis, u tvojem Django `settings.py` mora biti uključen Simple JWT: + +```python +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), +} + +``` + +Također, endpoint za prijavu unutar Django `urls.py` mora odgovarati stazi `token/` koju poziva `routes.login()`. + +### B. Podrška za pametno prepoznavanje Content-Type-a (JSON vs Multipart) + +Funkcija `getAuthHeaders()` dinamički prilagođava zaglavlja ovisno o tome šalju li se sirovi tekstualni podaci ili binarne datoteke s terena: + +```javascript +const isFormData = bodyData instanceof FormData; +if (!isFormData) { headers['Content-Type'] = 'application/json'; } + +``` + +* +**Za `createVozilo` i `patchNalog`:** Šalje se čisti `application/json`. Django mora imati uključen `JSONParser` (što je DRF default). + + +* +**Za `createNalog`:** Šalje se `FormData` objekt jer učitavaš slike s terena (`RadniNalogSlika`). Tvoj Django `RadniNalogViewSet` (ili pripadajuća funkcija) mora eksplicitno imati definiran `parser_classes = [MultiPartParser, FormParser]` kako bi ispravno razdvojio tekstualna polja od slikovnih datoteka unutar `request.data`. + + + +### C. Dinamičko filtriranje i Query Parameters na ViewSetovima + +Unutar `api.js`, objekt `routes` dinamički gradi query stringove za filtriranje: + +* +`radniNalozi(params)` gradi string poput `operativa/radni-nalozi/?status=u_radu` ili filtrira prema vozilu. + + +* +`strojevi(vlasnikId)` gradi stazu `fleet/strojevi/?vlasnik=id`. + + + +Da bi ovo radilo, tvoji DRF ViewSetovi na backendu moraju implementirati `django-filter` backend ili ručno prepisati `get_queryset()` metodu: + +```python +# Primjer na backendu za fleet/strojevi/ +def get_queryset(self): + queryset = Stroj.objects.all() + vlasnik_id = self.request.query_params.get('vlasnik') + if vlasnik_id: + queryset = queryset.filter(vlasnik_id=vlasnik_id) + return queryset + +``` + +### D. CORS (Cross-Origin Resource Sharing) Propusnost + +Budući da Astro aplikacija šalje HTTP zahtjeve s jedne adrese/porta na `PUBLIC_API_URL` (koji je definiran u `API_BASE`) , tvoj Django mora imati instaliran `django-cors-headers` paket te dopuštenu domenu frontenda kroz `CORS_ALLOWED_ORIGINS` konfiguraciju kako preglednik ne bi blokirao asinkrone `fetch` pozive. + + + +# Client-Side Rendering - CSR + +Evo kompletnog, tehnički strukturiranog i rafiniranog nacrta prezentacije za tvoj *ServisLog Terminal*. Odgovor je fokusiran **isključivo na priložene datoteke (`api.js`, `index.astro` i tvoj zadnji klijentski `RadniNalogLista.astro`)** te detaljno obuhvaća novu logiku sinkronizacije stanja između učitavanja podataka i upravljanja vizualnim elementima u `NaslovList.astro`. + +--- + +# PREZENTACIJA: Migracija na CSR i Sinkronizacija Stanja Sučelja + +## SLIJD 1: Arhitektura Autentifikacije i Tok Tokena (localStorage) + +Uklanjanjem ovisnosti o kolačićima na poslužitelju, aplikacija je prebačena na stopostotni klijentski model autorizacije. To eliminira serverske krahove (`Given token not valid...`) jer se provjera identiteta odvija unutar izoliranog okruženja preglednika. + +``` +[Klijent: login()] ───> POST /api/token/ ───> [Django REST Framework] + │ + (access & refresh) <───┘ + │ + localStorage.setItem('access_token', data.access) + │ +[Klijent: getAuthHeaders()] ──┴──> Headers: { Authorization: Bearer } + +``` + +### Tehnička mehanika unutar priloženih datoteka: + +* **Pohrana pri prijavi:** Asinkrona funkcija `login(email, password)` u `api.js` šalje korisničke podatke na backend. Nakon validacije, klijentski kôd presreće odgovor i trajno upisuje tokene u memoriju: +```javascript +localStorage.setItem('access_token', data.access); +localStorage.setItem('refresh_token', data.refresh); + +``` + + +* **Centralizirani presretač (getAuthHeaders):** Prije slanja bilo kojeg operativnog zahtjeva (poput `fetchDashboardData()`), funkcija `getAuthHeaders(bodyData)` provjerava postojanje `window` objekta kako bi sigurno pročitala memoriju klijenta: +```javascript +const token = typeof window !== 'undefined' ? localStorage.getItem('access_token') : null; + +``` + + +Ako token postoji, on se injektira u standardno HTTP zaglavlje: `headers['Authorization'] = 'Bearer ' + token`. + +--- + +## SLIJD 2: Životni vijek Tranzicije Sučelja (Loading -> Loaded) + +Klijentsko renderiranje donosi asinkroni životni vijek u kojem se elementi sučelja ne prikazuju odjednom, već se postupno aktiviraju (hidriraju) onog trenutka kada podaci stignu s mreže. + +### Tri faze tranzicije u sučelju: + +1. **Faza Učitavanja (Loading):** Poslužitelj isporučuje kostur stranice. Korisnik odmah vidi animirani krug (`#nalozi-loader`) s pulsirajućim tekstom *"Sinkronizacija radnih naloga..."*. Istovremeno, gornje kartice filtera s brojačima su **potpuno sakrivene** kako korisnik ne bi vidio nule i kako ne bi mogao okinuti preuranjeni klik. +2. **Faza Hidracije (Data Processing):** Klijentski JavaScript u pozadini izvršava `fetchDashboardData()`, prima sirovi niz naloga, preračunava statistiku (`planiranoCount`, `uRaduCount`), filtrira elemente prema stanjima iz URL-a i gradi HTML stabla. +3. **Faza Prikaza (Loaded):** Izvršava se atomska zamjena CSS klasa u DOM-u. Loader se skriva, generirane kartice radnih naloga se ubacuju u kontejner, a kartice s brojačima u zaglavlju glatko postaju vidljive s točnim, svježim vrijednostima. + +--- + +## SLIJD 3: Sinkronizacija Stanja preko Klase `#nalozi-loader` i Varijabli Sučelja + +Ovaj mehanizam rješava kritičan problem sinkronizacije: kako spriječiti prikaz praznih stat-kartica u `NaslovList.astro` dok `RadniNalogLista.astro` još uvijek čeka podatke s API-ja. + +### Implementacija u `NaslovList.astro`: + +Desni kontejner koji drži stat-kartice (`#stat-cards-container`) inicijalno se isporučuje s nultom vidljivošću i blokiranim interakcijama pomoću Tailwind pomoćnih klasa: + +```html +
+
+ +``` + +### Upravljanje stanjem unutar `RadniNalogLista.astro`: + +Unutar asinkrone funkcije `renderirajRadneNalogeKlijentski()`, stanje se mijenja izravnom manipulacijom DOM elemenata tek **nakon uspješnog `try` bloka**: + +```javascript +// 1. Upisivanje svježe proračunatih vrijednosti u DOM podkomponente +const planiranoValue = sekcija.querySelector('[data-filter="planirano"] .count-value'); +if (planiranoValue) planiranoValue.textContent = planiranoCount.toString(); + +const uRaduValue = sekcija.querySelector('[data-filter="u_radu"] .count-value'); +if (uRaduValue) uRaduValue.textContent = uRaduCount.toString(); + +// 2. TRANZICIJA STANJA: Uklanjanje loadera i aktivacija kartica +if (loader) { + loader.classList.add('hidden'); // Sakrivamo pulsirajući loader operacije +} + +if (statCardsContainer) { + // Gasimo nevidljivost i ponovno dopuštamo klikove na klijentske filtre + statCardsContainer.classList.remove('opacity-0', 'pointer-events-none'); + statCardsContainer.classList.add('opacity-100'); +} + +``` + +--- + +## SLIJD 4: Preduvjeti na Django REST Frameworku (Backend) + +Da bi klijentski kod iz `api.js` i `RadniNalogLista.astro` radio bez pogrešaka, DRF mora striktno podržavati četiri arhitektonska standarda: + +* **CORS (Cross-Origin Resource Sharing) Propusnost:** Budući da Astro šalje asinkrone fetch zahtjeve s klijenta (`localhost:4321`) na domenu backenda, u Django `settings.py` mora biti uključen `django-cors-headers` middleware, a adresa frontenda mora biti upisana u `CORS_ALLOWED_ORIGINS` listu. +* **Simple JWT Validacija:** Backend mora prepoznati i dekodirati `Authorization: Bearer ` zaglavlje koje generira funkcija `getAuthHeaders()`. Polje odgovora na `/api/token/` endpointu mora vraćati objekt s ključem `access`. +* **Multi-Parser Podrška (JSON vs Multipart):** Funkcija `getAuthHeaders()` u `api.js` provjerava tip podataka prije slanja: +```javascript +const isFormData = bodyData instanceof FormData; +if (!isFormData) { headers['Content-Type'] = 'application/json'; } + +``` + + +To znači da Django ViewSetovi moraju imati omogućene odgovarajuće parsere. Za bazične preglede (`fetchDashboardData`) koristi se `JSONParser`, dok za funkciju kreiranja naloga sa slikama s terena (`createNalog`), Django klasa mora imati `parser_classes = [MultiPartParser, FormParser]`. +* **Query Parametri za Filtriranje:** Kako bi klijentski URL parametri poput `?status=u_radu` vratili ispravne podatke, Django ViewSet mora presretati zahtjeve i filtrirati SQL upite na razini baze kroz `get_queryset()` metodu ili preko `DjangoFilterBackend` paketa prije slanja JSON-a natrag u Astro. + + +# Server-Side Rendering - SSR + +U priloženom kodu datoteke `RadniNalogLista.astro` koji je postavljen kao **SSR (Server-Side Rendering)** komponenta, klasa **`nalozi-loader` uopće ne postoji niti se koristi**. + +Međutim, ako tu komponentu želimo prebaciti na **klijentsko renderiranje (Client-side rendering)** kako bi vukla podatke izravno iz `localStorage`-a u pregledniku, uvođenje klase/ID-ja `nalozi-loader` postaje ključni mehanizam za upravljanje stanjem sučelja. + +Evo detaljnog obrazloženja kako taj mehanizam točno funkcionira kroz životni vijek klijentske komponente, podijeljenog u tri cjeline: + +--- + +## 1. Konceptualni prikaz: Životni vijek tranzicije sučelja + +Kada se učitavanje prebaci na klijenta, sučelje prolazi kroz asinkroni proces zamjene elemenata u DOM-u: + +``` +[Klijent otvara stranicu] + │ + ├──> Renderira se samo statični HTML skeleton s loaderom + │ (Vidljiv element: #nalozi-loader, Sakriven element: #nalozi-list) + │ + ├──> Okida se asinkroni JavaScript: fetchDashboardData() + │ (Preglednik čita JWT token iz localStorage-a i šalje zahtjev) + │ +[Podaci stigli s Django API-ja] + │ + ├──> JS generira HTML kartice unutar #nalozi-list + │ + └──> ATOMSKA ZAMJENA STANJA (Pomoću CSS klasa): + #nalozi-loader ──> .classList.add('hidden') + #nalozi-list ──> .classList.remove('hidden') + +``` + +--- + +## 2. Implementacija unutar strukture `RadniNalogLista.astro` + +Unutar samog Astro HTML koda (ispod frontmattera), struktura se postavlja tako da loader zauzima cijeli prostor predviđen za listu, sprječavajući "skakanje" sučelja (*layout shift*) dok se podaci čekaju: + +```html +
+ + +
+ +
+ + Sinkronizacija radnih naloga... +
+ +
+
+
+ +``` + +--- + +## 3. Kako JavaScript upravlja loaderom i stanjem (Loaded) + +Unutar ` + +``` + +### Zašto je ovaj pristup s `nalozi-loader` tehnički superiorniji za klijentski render? + +1. **Nema praznog prostora:** Korisnik odmah dobiva povratnu informaciju (animirani krug i pulsirajući tekst) da aplikacija komunicira s Proxmox/Django backendom. +2. **JWT Sigurnost:** Budući da se `fetchDashboardData()` izvršava tek unutar ovog klijentskog skripta, funkcija uspješno čita token iz `localStorage.getItem('access_token')`, što u potpunosti rješava grešku `Given token not valid for any token type` s kojom si se susretao na serverskoj strani aplikacije. \ No newline at end of file diff --git a/biljeske/LOKALIZACIJA_CDN_RESURSA.md b/biljeske/LOKALIZACIJA_CDN_RESURSA.md new file mode 100644 index 0000000..1999e8f --- /dev/null +++ b/biljeske/LOKALIZACIJA_CDN_RESURSA.md @@ -0,0 +1,160 @@ +## Layout.astro + +```astro +--- +// src/layouts/Layout.astro +import Nav from "../components/Navbar.astro"; +import Toast from '../components/Toast.astro'; +import site from "../data/site.json"; +import "../styles/global.css"; // LOKALNI RESURSI + +interface Props { + title?: string; + description?: string; +} + +const { + title = site.title, + description = site.description +} = Astro.props; +--- + + + + + + + + + + + {title} | {site.title} + + + + + + + +