diff --git a/parks/__pycache__/models.cpython-312.pyc b/parks/__pycache__/models.cpython-312.pyc index 84fa0c6a..98aaed34 100644 Binary files a/parks/__pycache__/models.cpython-312.pyc and b/parks/__pycache__/models.cpython-312.pyc differ diff --git a/parks/__pycache__/urls.cpython-312.pyc b/parks/__pycache__/urls.cpython-312.pyc index 03333a7c..b2afbce0 100644 Binary files a/parks/__pycache__/urls.cpython-312.pyc and b/parks/__pycache__/urls.cpython-312.pyc differ diff --git a/parks/__pycache__/views.cpython-312.pyc b/parks/__pycache__/views.cpython-312.pyc index 5f4885e1..330dd848 100644 Binary files a/parks/__pycache__/views.cpython-312.pyc and b/parks/__pycache__/views.cpython-312.pyc differ diff --git a/parks/forms.py b/parks/forms.py new file mode 100644 index 00000000..020c9c93 --- /dev/null +++ b/parks/forms.py @@ -0,0 +1,125 @@ +from django import forms +from django.urls import reverse_lazy +from .models import Park +from cities_light.models import Country, Region, City + +class ParkForm(forms.ModelForm): + # Hidden fields for actual model relations + country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True, widget=forms.HiddenInput()) + region = forms.ModelChoiceField(queryset=Region.objects.all(), required=False, widget=forms.HiddenInput()) + city = forms.ModelChoiceField(queryset=City.objects.all(), required=False, widget=forms.HiddenInput()) + + # Visible fields for Awesomplete + country_name = forms.CharField( + label="Country", + widget=forms.TextInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'placeholder': 'Start typing a country name...', + }) + ) + region_name = forms.CharField( + label="Region/State", + required=False, + widget=forms.TextInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'placeholder': 'Start typing a region/state name...', + }) + ) + city_name = forms.CharField( + label="City", + required=False, + widget=forms.TextInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'placeholder': 'Start typing a city name...', + }) + ) + + class Meta: + model = Park + fields = ['name', 'country', 'region', 'city', 'description', 'owner', 'status', + 'opening_date', 'closing_date', 'operating_season', 'size_acres', 'website'] + widgets = { + 'name': forms.TextInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'description': forms.Textarea(attrs={ + 'rows': 4, + 'class': 'w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'owner': forms.Select(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'status': forms.Select(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'opening_date': forms.DateInput(attrs={ + 'type': 'date', + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'closing_date': forms.DateInput(attrs={ + 'type': 'date', + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), + 'operating_season': forms.TextInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'placeholder': 'e.g., Year-round, Summer only, etc.' + }), + 'size_acres': forms.NumberInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'step': '0.01', + 'min': '0' + }), + 'website': forms.URLInput(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white', + 'placeholder': 'https://example.com' + }), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + instance = kwargs.get('instance') + if instance: + if instance.country: + self.fields['country_name'].initial = instance.country.name + self.fields['country'].initial = instance.country + if instance.region: + self.fields['region_name'].initial = instance.region.name + self.fields['region'].initial = instance.region + if instance.city: + self.fields['city_name'].initial = instance.city.name + self.fields['city'].initial = instance.city + + def clean(self): + cleaned_data = super().clean() + country_name = cleaned_data.get('country_name') + region_name = cleaned_data.get('region_name') + city_name = cleaned_data.get('city_name') + + if country_name: + try: + country = Country.objects.get(name__iexact=country_name) + cleaned_data['country'] = country + except Country.DoesNotExist: + self.add_error('country_name', 'Invalid country name') + + if region_name and cleaned_data.get('country'): + try: + region = Region.objects.get( + name__iexact=region_name, + country=cleaned_data['country'] + ) + cleaned_data['region'] = region + except Region.DoesNotExist: + self.add_error('region_name', 'Invalid region name for selected country') + + if city_name and cleaned_data.get('region'): + try: + city = City.objects.get( + name__iexact=city_name, + region=cleaned_data['region'] + ) + cleaned_data['city'] = city + except City.DoesNotExist: + self.add_error('city_name', 'Invalid city name for selected region') + + return cleaned_data diff --git a/parks/migrations/0004_historicalpark_city_historicalpark_state_park_city_and_more.py b/parks/migrations/0004_historicalpark_city_historicalpark_state_park_city_and_more.py new file mode 100644 index 00000000..06606ebd --- /dev/null +++ b/parks/migrations/0004_historicalpark_city_historicalpark_state_park_city_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.1.2 on 2024-10-30 23:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("parks", "0003_alter_historicalpark_status_alter_park_status"), + ] + + operations = [ + migrations.AddField( + model_name="historicalpark", + name="city", + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name="historicalpark", + name="state", + field=models.CharField( + blank=True, help_text="State/Province/Region", max_length=255 + ), + ), + migrations.AddField( + model_name="park", + name="city", + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name="park", + name="state", + field=models.CharField( + blank=True, help_text="State/Province/Region", max_length=255 + ), + ), + ] diff --git a/parks/migrations/0005_update_country_field_length.py b/parks/migrations/0005_update_country_field_length.py new file mode 100644 index 00000000..16647b77 --- /dev/null +++ b/parks/migrations/0005_update_country_field_length.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.2 on 2024-10-30 23:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("parks", "0004_historicalpark_city_historicalpark_state_park_city_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="historicalpark", + name="country", + field=models.CharField(help_text="Country name", max_length=255), + ), + migrations.AlterField( + model_name="park", + name="country", + field=models.CharField(help_text="Country name", max_length=255), + ), + ] diff --git a/parks/migrations/0006_update_location_fields_to_cities_light.py b/parks/migrations/0006_update_location_fields_to_cities_light.py new file mode 100644 index 00000000..a1572e15 --- /dev/null +++ b/parks/migrations/0006_update_location_fields_to_cities_light.py @@ -0,0 +1,125 @@ +from django.db import migrations, models +import django.db.models.deletion + +def forwards_func(apps, schema_editor): + # Get the historical models + Park = apps.get_model("parks", "Park") + Country = apps.get_model("cities_light", "Country") + Region = apps.get_model("cities_light", "Region") + City = apps.get_model("cities_light", "City") + + # Create default country for existing parks + default_country, _ = Country.objects.get_or_create( + name='Unknown', + name_ascii='Unknown', + slug='unknown', + code2='XX' + ) + + # Store old values + parks_data = [] + for park in Park.objects.all(): + parks_data.append({ + 'id': park.id, + 'old_country': park.country, + 'old_state': park.state, + 'location': park.location + }) + + # Remove old fields first + Park._meta.get_field('country').null = True + Park._meta.get_field('state').null = True + Park.objects.all().update(country=None, state=None) + + # Now update with new values + for data in parks_data: + park = Park.objects.get(id=data['id']) + park.country_id = default_country.id + park.save() + +def reverse_func(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('cities_light', '0011_alter_city_country_alter_city_region_and_more'), + ('parks', '0005_update_country_field_length'), + ] + + operations = [ + # First make the fields nullable + migrations.AlterField( + model_name='park', + name='country', + field=models.CharField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='historicalpark', + name='country', + field=models.CharField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='park', + name='state', + field=models.CharField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='historicalpark', + name='state', + field=models.CharField(max_length=255, null=True), + ), + + # Run the data migration + migrations.RunPython(forwards_func, reverse_func), + + # Remove old fields + migrations.RemoveField( + model_name='park', + name='state', + ), + migrations.RemoveField( + model_name='historicalpark', + name='state', + ), + migrations.RemoveField( + model_name='park', + name='country', + ), + migrations.RemoveField( + model_name='historicalpark', + name='country', + ), + + # Add new fields + migrations.AddField( + model_name='park', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.country'), + ), + migrations.AddField( + model_name='park', + name='region', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.region'), + ), + migrations.AddField( + model_name='park', + name='city', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.city'), + ), + migrations.AddField( + model_name='historicalpark', + name='country', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.country'), + ), + migrations.AddField( + model_name='historicalpark', + name='region', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.region'), + ), + migrations.AddField( + model_name='historicalpark', + name='city', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.city'), + ), + ] diff --git a/parks/models.py b/parks/models.py index 3489c709..75915989 100644 --- a/parks/models.py +++ b/parks/models.py @@ -2,7 +2,7 @@ from django.db import models from django.contrib.contenttypes.fields import GenericRelation from django.utils.text import slugify from simple_history.models import HistoricalRecords -import pycountry +from cities_light.models import Country, Region, City class Park(models.Model): STATUS_CHOICES = [ @@ -17,7 +17,9 @@ class Park(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(max_length=255, unique=True) location = models.CharField(max_length=255) - country = models.CharField(max_length=2, help_text='Two-letter country code (ISO 3166-1 alpha-2)') + country = models.ForeignKey(Country, on_delete=models.PROTECT) + region = models.ForeignKey(Region, on_delete=models.PROTECT, null=True, blank=True) + city = models.ForeignKey(City, on_delete=models.PROTECT, null=True, blank=True) description = models.TextField(blank=True) owner = models.ForeignKey( 'companies.Company', @@ -62,6 +64,20 @@ class Park(models.Model): def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) + + # Update the location field to combine country, region, and city + location_parts = [] + if self.city: + location_parts.append(self.city.name) + if self.region: + location_parts.append(self.region.name) + if self.country: + location_parts.append(self.country.name) + + # Only update location if we have parts to combine + if location_parts: + self.location = ', '.join(location_parts) + super().save(*args, **kwargs) @classmethod @@ -76,13 +92,16 @@ class Park(models.Model): return cls.objects.get(id=history.id), True raise cls.DoesNotExist("No park found with this slug") - def get_country_name(self): - """Get the full country name from the country code""" - try: - country = pycountry.countries.get(alpha_2=self.country) - return country.name if country else self.country - except: - return self.country + def get_formatted_location(self): + """Get a formatted location string combining city, region, and country""" + location_parts = [] + if self.city: + location_parts.append(self.city.name) + if self.region: + location_parts.append(self.region.name) + if self.country: + location_parts.append(self.country.name) + return ', '.join(location_parts) class ParkArea(models.Model): name = models.CharField(max_length=255) diff --git a/parks/urls.py b/parks/urls.py index 20dcc4ec..748c128f 100644 --- a/parks/urls.py +++ b/parks/urls.py @@ -8,8 +8,10 @@ urlpatterns = [ path('', views.ParkListView.as_view(), name='park_list'), path('create/', views.ParkCreateView.as_view(), name='park_create'), path('rides/', RideListView.as_view(), name='all_rides'), # Global rides list - path('countries/search/', views.search_countries, name='search_countries'), - path('countries/select/', views.select_country, name='select_country'), + path('ajax/countries/', views.get_countries, name='get_countries'), + path('ajax/regions/', views.get_regions, name='get_regions'), + path('ajax/cities/', views.get_cities, name='get_cities'), + path('ajax/locations/', views.get_locations, name='get_locations'), path('/', views.ParkDetailView.as_view(), name='park_detail'), path('/rides/', include('rides.urls', namespace='rides')), ] diff --git a/parks/views.py b/parks/views.py index f8adc786..d7bf5553 100644 --- a/parks/views.py +++ b/parks/views.py @@ -7,17 +7,66 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.http import JsonResponse, HttpResponseRedirect, HttpResponse from .models import Park, ParkArea +from .forms import ParkForm from rides.models import Ride from core.views import SlugRedirectMixin from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin from moderation.models import EditSubmission -import pycountry +from cities_light.models import Country, Region, City + +def get_countries(request): + query = request.GET.get('q', '') + countries = Country.objects.filter(name__icontains=query).values_list('name', flat=True)[:10] + return JsonResponse(list(countries), safe=False) + +def get_regions(request): + query = request.GET.get('q', '') + country = request.GET.get('country', '') + if not country: + return JsonResponse([], safe=False) + + regions = Region.objects.filter( + Q(name__icontains=query) | Q(alternate_names__icontains=query), + country__name__iexact=country + ).values_list('name', flat=True)[:10] + return JsonResponse(list(regions), safe=False) + +def get_cities(request): + query = request.GET.get('q', '') + region = request.GET.get('region', '') + country = request.GET.get('country', '') + if not region or not country: + return JsonResponse([], safe=False) + + cities = City.objects.filter( + Q(name__icontains=query) | Q(alternate_names__icontains=query), + region__name__iexact=region, + region__country__name__iexact=country + ).values_list('name', flat=True)[:10] + return JsonResponse(list(cities), safe=False) + +def get_locations(request): + query = request.GET.get('q', '') + locations = set() + + # Search countries + countries = Country.objects.filter(name__icontains=query).values_list('name', flat=True)[:5] + locations.update(countries) + + # Search regions + regions = Region.objects.filter(name__icontains=query).values_list('name', flat=True)[:5] + locations.update(regions) + + # Search cities + cities = City.objects.filter(name__icontains=query).values_list('name', flat=True)[:5] + locations.update(cities) + + return JsonResponse(list(locations), safe=False) class ParkCreateView(LoginRequiredMixin, CreateView): model = Park + form_class = ParkForm template_name = 'parks/park_form.html' - fields = ['name', 'location', 'country', 'description', 'owner', 'status', - 'opening_date', 'closing_date', 'operating_season', 'size_acres', 'website'] def form_valid(self, form): # If user is moderator or above, save directly @@ -30,6 +79,12 @@ class ParkCreateView(LoginRequiredMixin, CreateView): # Convert model instances to IDs for JSON serialization if cleaned_data.get('owner'): cleaned_data['owner'] = cleaned_data['owner'].id + if cleaned_data.get('country'): + cleaned_data['country'] = cleaned_data['country'].id + if cleaned_data.get('region'): + cleaned_data['region'] = cleaned_data['region'].id + if cleaned_data.get('city'): + cleaned_data['city'] = cleaned_data['city'].id submission = EditSubmission.objects.create( user=self.request.user, @@ -39,41 +94,10 @@ class ParkCreateView(LoginRequiredMixin, CreateView): reason=self.request.POST.get('reason', ''), source=self.request.POST.get('source', '') ) - return HttpResponseRedirect(reverse('park_list')) + return HttpResponseRedirect(reverse('parks:park_list')) def get_success_url(self): - return reverse('park_detail', kwargs={'slug': self.object.slug}) - -def search_countries(request): - query = request.GET.get('q', '').strip() - countries = [] - - if query: - # Use pycountry's search functionality for fuzzy matching - try: - # Try exact search first - country = pycountry.countries.get(name=query) - if country: - countries = [country] - else: - # If no exact match, try fuzzy search - countries = pycountry.countries.search_fuzzy(query) - except LookupError: - # If search fails, fallback to manual filtering - countries = [ - country for country in pycountry.countries - if query.lower() in country.name.lower() - ] - - return render(request, 'parks/partials/country_search_results.html', { - 'countries': countries[:10] # Limit to top 10 results - }) - -def select_country(request): - if request.method == 'POST': - country = request.POST.get('country', '') - return HttpResponse(country) - return HttpResponse('Invalid request', status=400) + return reverse('parks:park_detail', kwargs={'slug': self.object.slug}) class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView): model = Park @@ -96,7 +120,7 @@ class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixi return context def get_redirect_url_pattern(self): - return 'park_detail' + return 'parks:park_detail' class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView): model = ParkArea @@ -123,7 +147,7 @@ class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmission return context def get_redirect_url_pattern(self): - return 'park_detail' + return 'parks:park_detail' def get_redirect_url_kwargs(self): return { @@ -137,7 +161,7 @@ class ParkListView(ListView): context_object_name = 'parks' def get_queryset(self): - queryset = Park.objects.select_related('owner').prefetch_related('photos', 'rides') + queryset = Park.objects.select_related('owner', 'country', 'region', 'city').prefetch_related('photos', 'rides') search = self.request.GET.get('search', '').strip() or None location = self.request.GET.get('location', '').strip() or None @@ -146,10 +170,19 @@ class ParkListView(ListView): if search: queryset = queryset.filter( Q(name__icontains=search) | - Q(location__icontains=search) + Q(location__icontains=search) | + Q(country__name__icontains=search) | + Q(region__name__icontains=search) | + Q(city__name__icontains=search) ) if location: - queryset = queryset.filter(location=location) + # Try to match against the formatted location or any location field + queryset = queryset.filter( + Q(location__icontains=location) | + Q(country__name__icontains=location) | + Q(region__name__icontains=location) | + Q(city__name__icontains=location) + ) if status: queryset = queryset.filter(status=status) @@ -157,18 +190,11 @@ class ParkListView(ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - - # Get unique locations for filter dropdown - context['locations'] = list(Park.objects.values_list('location', flat=True) - .distinct().order_by('location')) - - # Add current filter values to context context['current_filters'] = { 'search': self.request.GET.get('search', ''), 'location': self.request.GET.get('location', ''), 'status': self.request.GET.get('status', '') } - return context def get(self, request, *args, **kwargs): diff --git a/assets/css/src/input.css b/static/css/src/input.css similarity index 98% rename from assets/css/src/input.css rename to static/css/src/input.css index b3f790c6..c0c7d323 100644 --- a/assets/css/src/input.css +++ b/static/css/src/input.css @@ -221,7 +221,7 @@ } .btn-discord { - @apply bg-[#5865F2] hover:bg-[#4752C4] text-white border-transparent shadow-[#5865F2]/20; + @apply text-gray-700 bg-white border-gray-200 hover:bg-gray-50 shadow-gray-200/50 dark:shadow-gray-900/50; } .btn-google { diff --git a/static/css/tailwind.css b/static/css/tailwind.css index da14dc3b..8eb604be 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -1509,11 +1509,6 @@ select { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -#mobileMenu.show { - max-height: 300px; - opacity: 1; -} - #mobileMenu .space-y-4 { padding-bottom: 1.5rem; } @@ -1915,28 +1910,6 @@ select { color: rgb(209 213 219 / var(--tw-text-opacity)); } -.form-hint { - margin-top: 0.5rem; -} - -.form-hint > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); -} - -.form-hint { - font-size: 0.875rem; - line-height: 1.25rem; - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); -} - -.form-hint:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(156 163 175 / var(--tw-text-opacity)); -} - .form-error { margin-top: 0.5rem; font-size: 0.875rem; @@ -2162,18 +2135,24 @@ select { } .btn-discord { - border-color: transparent; + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); --tw-bg-opacity: 1; - background-color: rgb(88 101 242 / var(--tw-bg-opacity)); + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - --tw-shadow-color: rgb(88 101 242 / 0.2); + color: rgb(55 65 81 / var(--tw-text-opacity)); + --tw-shadow-color: rgb(229 231 235 / 0.5); --tw-shadow: var(--tw-shadow-colored); } .btn-discord:hover { --tw-bg-opacity: 1; - background-color: rgb(71 82 196 / var(--tw-bg-opacity)); + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.btn-discord:is(.dark *) { + --tw-shadow-color: rgb(17 24 39 / 0.5); + --tw-shadow: var(--tw-shadow-colored); } .btn-google { @@ -2211,23 +2190,6 @@ select { backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); } -.alert-success { - border-width: 1px; - --tw-border-opacity: 1; - border-color: rgb(187 247 208 / var(--tw-border-opacity)); - background-color: rgb(220 252 231 / 0.9); - --tw-text-opacity: 1; - color: rgb(22 101 52 / var(--tw-text-opacity)); -} - -.alert-success:is(.dark *) { - --tw-border-opacity: 1; - border-color: rgb(21 128 61 / var(--tw-border-opacity)); - background-color: rgb(22 101 52 / 0.3); - --tw-text-opacity: 1; - color: rgb(220 252 231 / var(--tw-text-opacity)); -} - .alert-error { border-width: 1px; --tw-border-opacity: 1; @@ -2245,40 +2207,6 @@ select { color: rgb(254 226 226 / var(--tw-text-opacity)); } -.alert-warning { - border-width: 1px; - --tw-border-opacity: 1; - border-color: rgb(254 240 138 / var(--tw-border-opacity)); - background-color: rgb(254 249 195 / 0.9); - --tw-text-opacity: 1; - color: rgb(133 77 14 / var(--tw-text-opacity)); -} - -.alert-warning:is(.dark *) { - --tw-border-opacity: 1; - border-color: rgb(161 98 7 / var(--tw-border-opacity)); - background-color: rgb(133 77 14 / 0.3); - --tw-text-opacity: 1; - color: rgb(254 249 195 / var(--tw-text-opacity)); -} - -.alert-info { - border-width: 1px; - --tw-border-opacity: 1; - border-color: rgb(191 219 254 / var(--tw-border-opacity)); - background-color: rgb(219 234 254 / 0.9); - --tw-text-opacity: 1; - color: rgb(30 64 175 / var(--tw-text-opacity)); -} - -.alert-info:is(.dark *) { - --tw-border-opacity: 1; - border-color: rgb(29 78 216 / var(--tw-border-opacity)); - background-color: rgb(30 64 175 / 0.3); - --tw-text-opacity: 1; - color: rgb(219 234 254 / var(--tw-text-opacity)); -} - /* Layout Components */ .card { @@ -2299,76 +2227,8 @@ select { background-color: rgb(31 41 55 / var(--tw-bg-opacity)); } -.card-hover { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); - transition-property: transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.card-hover:hover { - --tw-translate-y: -0.25rem; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.grid-cards { - display: grid; - grid-template-columns: repeat(1, minmax(0, 1fr)); - gap: 1.5rem; -} - -@media (min-width: 768px) { - .grid-cards { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -@media (min-width: 1024px) { - .grid-cards { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - /* Typography */ -.heading-1 { - margin-bottom: 1.5rem; - font-size: 1.875rem; - line-height: 2.25rem; - font-weight: 700; - --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity)); -} - -.heading-1:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); -} - -.heading-2 { - margin-bottom: 1rem; - font-size: 1.5rem; - line-height: 2rem; - font-weight: 700; - --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity)); -} - -.heading-2:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); -} - -.text-body { - --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity)); -} - -.text-body:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(209 213 219 / var(--tw-text-opacity)); -} - /* Turnstile Widget */ .turnstile { @@ -2395,10 +2255,6 @@ select { right: 0px; } -.top-1\/2 { - top: 50%; -} - .z-10 { z-index: 10; } @@ -2425,11 +2281,6 @@ select { margin-right: auto; } -.my-6 { - margin-top: 1.5rem; - margin-bottom: 1.5rem; -} - .mb-1 { margin-bottom: 0.25rem; } @@ -2522,6 +2373,10 @@ select { display: inline-flex; } +.table { + display: table; +} + .grid { display: grid; } @@ -2554,10 +2409,6 @@ select { height: 2rem; } -.max-h-\[300px\] { - max-height: 300px; -} - .min-h-\[calc\(100vh-16rem\)\] { min-height: calc(100vh - 16rem); } @@ -2566,10 +2417,6 @@ select { min-height: 100vh; } -.w-1\/3 { - width: 33.333333%; -} - .w-24 { width: 6rem; } @@ -2734,10 +2581,6 @@ select { border-radius: 0.25rem; } -.rounded-2xl { - border-radius: 1rem; -} - .rounded-full { border-radius: 9999px; } @@ -2750,10 +2593,6 @@ select { border-radius: 0.375rem; } -.rounded-xl { - border-radius: 0.75rem; -} - .rounded-l-lg { border-top-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem; @@ -2776,11 +2615,6 @@ select { border-top-width: 1px; } -.border-blue-200 { - --tw-border-opacity: 1; - border-color: rgb(191 219 254 / var(--tw-border-opacity)); -} - .border-gray-200 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); @@ -2795,21 +2629,11 @@ select { border-color: rgb(209 213 219 / var(--tw-border-opacity)); } -.border-green-200 { - --tw-border-opacity: 1; - border-color: rgb(187 247 208 / var(--tw-border-opacity)); -} - .border-green-500 { --tw-border-opacity: 1; border-color: rgb(34 197 94 / var(--tw-border-opacity)); } -.border-red-200 { - --tw-border-opacity: 1; - border-color: rgb(254 202 202 / var(--tw-border-opacity)); -} - .border-red-400 { --tw-border-opacity: 1; border-color: rgb(248 113 113 / var(--tw-border-opacity)); @@ -2820,29 +2644,11 @@ select { border-color: rgb(239 68 68 / var(--tw-border-opacity)); } -.border-transparent { - border-color: transparent; -} - -.border-yellow-200 { - --tw-border-opacity: 1; - border-color: rgb(254 240 138 / var(--tw-border-opacity)); -} - -.bg-\[\#5865F2\] { - --tw-bg-opacity: 1; - background-color: rgb(88 101 242 / var(--tw-bg-opacity)); -} - .bg-blue-100 { --tw-bg-opacity: 1; background-color: rgb(219 234 254 / var(--tw-bg-opacity)); } -.bg-blue-100\/90 { - background-color: rgb(219 234 254 / 0.9); -} - .bg-blue-50 { --tw-bg-opacity: 1; background-color: rgb(239 246 255 / var(--tw-bg-opacity)); @@ -2873,28 +2679,16 @@ select { background-color: rgb(220 252 231 / var(--tw-bg-opacity)); } -.bg-green-100\/90 { - background-color: rgb(220 252 231 / 0.9); -} - .bg-green-600 { --tw-bg-opacity: 1; background-color: rgb(22 163 74 / var(--tw-bg-opacity)); } -.bg-primary\/10 { - background-color: rgb(79 70 229 / 0.1); -} - .bg-red-100 { --tw-bg-opacity: 1; background-color: rgb(254 226 226 / var(--tw-bg-opacity)); } -.bg-red-100\/90 { - background-color: rgb(254 226 226 / 0.9); -} - .bg-red-600 { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); @@ -2905,10 +2699,6 @@ select { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-white\/70 { - background-color: rgb(255 255 255 / 0.7); -} - .bg-white\/90 { background-color: rgb(255 255 255 / 0.9); } @@ -2918,10 +2708,6 @@ select { background-color: rgb(254 249 195 / var(--tw-bg-opacity)); } -.bg-yellow-100\/90 { - background-color: rgb(254 249 195 / 0.9); -} - .bg-gradient-to-br { background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); } @@ -2981,11 +2767,6 @@ select { padding: 2rem; } -.px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; -} - .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -3031,11 +2812,6 @@ select { padding-bottom: 0.5rem; } -.py-2\.5 { - padding-top: 0.625rem; - padding-bottom: 0.625rem; -} - .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -3079,11 +2855,6 @@ select { line-height: 2.5rem; } -.text-base { - font-size: 1rem; - line-height: 1.5rem; -} - .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -3210,10 +2981,6 @@ select { color: rgb(133 77 14 / var(--tw-text-opacity)); } -.opacity-0 { - opacity: 0; -} - .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); @@ -3238,17 +3005,6 @@ select { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow-xl { - --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.shadow-gray-200\/50 { - --tw-shadow-color: rgb(229 231 235 / 0.5); - --tw-shadow: var(--tw-shadow-colored); -} - .ring-2 { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); @@ -3269,12 +3025,6 @@ select { backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); } -.backdrop-blur-sm { - --tw-backdrop-blur: blur(4px); - -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); - backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); -} - .transition-all { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -3293,19 +3043,6 @@ select { transition-duration: 150ms; } -.duration-300 { - transition-duration: 300ms; -} - -.ease-in-out { - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - -.content-\[\'\'\] { - --tw-content: ''; - content: var(--tw-content); -} - .dark\:prose-invert:is(.dark *) { --tw-prose-body: var(--tw-prose-invert-body); --tw-prose-headings: var(--tw-prose-invert-headings); @@ -3327,11 +3064,6 @@ select { --tw-prose-td-borders: var(--tw-prose-invert-td-borders); } -.first\:rounded-t-lg:first-child { - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem; -} - .last\:mb-0:last-child { margin-bottom: 0px; } @@ -3352,21 +3084,6 @@ select { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.hover\:scale-\[1\.02\]:hover { - --tw-scale-x: 1.02; - --tw-scale-y: 1.02; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.hover\:border-primary\/20:hover { - border-color: rgb(79 70 229 / 0.2); -} - -.hover\:bg-\[\#4752C4\]:hover { - --tw-bg-opacity: 1; - background-color: rgb(71 82 196 / var(--tw-bg-opacity)); -} - .hover\:bg-blue-700:hover { --tw-bg-opacity: 1; background-color: rgb(29 78 216 / var(--tw-bg-opacity)); @@ -3377,11 +3094,6 @@ select { background-color: rgb(243 244 246 / var(--tw-bg-opacity)); } -.hover\:bg-gray-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)); -} - .hover\:bg-gray-300:hover { --tw-bg-opacity: 1; background-color: rgb(209 213 219 / var(--tw-bg-opacity)); @@ -3397,25 +3109,11 @@ select { background-color: rgb(21 128 61 / var(--tw-bg-opacity)); } -.hover\:bg-primary\/10:hover { - background-color: rgb(79 70 229 / 0.1); -} - .hover\:bg-red-700:hover { --tw-bg-opacity: 1; background-color: rgb(185 28 28 / var(--tw-bg-opacity)); } -.hover\:from-primary\/90:hover { - --tw-gradient-from: rgb(79 70 229 / 0.9) var(--tw-gradient-from-position); - --tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); -} - -.hover\:to-secondary\/90:hover { - --tw-gradient-to: rgb(225 29 72 / 0.9) var(--tw-gradient-to-position); -} - .hover\:text-blue-500:hover { --tw-text-opacity: 1; color: rgb(59 130 246 / var(--tw-text-opacity)); @@ -3505,10 +3203,6 @@ select { background-color: rgb(29 78 216 / var(--tw-bg-opacity)); } -.dark\:bg-blue-800\/30:is(.dark *) { - background-color: rgb(30 64 175 / 0.3); -} - .dark\:bg-blue-900:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(30 58 138 / var(--tw-bg-opacity)); @@ -3537,10 +3231,6 @@ select { background-color: rgb(31 41 55 / var(--tw-bg-opacity)); } -.dark\:bg-gray-800\/70:is(.dark *) { - background-color: rgb(31 41 55 / 0.7); -} - .dark\:bg-gray-800\/90:is(.dark *) { background-color: rgb(31 41 55 / 0.9); } @@ -3550,15 +3240,6 @@ select { background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } -.dark\:bg-green-700:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(21 128 61 / var(--tw-bg-opacity)); -} - -.dark\:bg-green-800\/30:is(.dark *) { - background-color: rgb(22 101 52 / 0.3); -} - .dark\:bg-green-900:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(20 83 45 / var(--tw-bg-opacity)); @@ -3569,15 +3250,6 @@ select { background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } -.dark\:bg-red-700:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(185 28 28 / var(--tw-bg-opacity)); -} - -.dark\:bg-red-800\/30:is(.dark *) { - background-color: rgb(153 27 27 / 0.3); -} - .dark\:bg-red-900:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(127 29 29 / var(--tw-bg-opacity)); @@ -3592,10 +3264,6 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } -.dark\:bg-yellow-800\/30:is(.dark *) { - background-color: rgb(133 77 14 / 0.3); -} - .dark\:bg-yellow-900:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(113 63 18 / var(--tw-bg-opacity)); @@ -3616,11 +3284,6 @@ select { --tw-gradient-to: #3b0764 var(--tw-gradient-to-position); } -.dark\:text-blue-100:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(219 234 254 / var(--tw-text-opacity)); -} - .dark\:text-blue-200:is(.dark *) { --tw-text-opacity: 1; color: rgb(191 219 254 / var(--tw-text-opacity)); @@ -3651,26 +3314,11 @@ select { color: rgb(156 163 175 / var(--tw-text-opacity)); } -.dark\:text-green-100:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(220 252 231 / var(--tw-text-opacity)); -} - .dark\:text-green-200:is(.dark *) { --tw-text-opacity: 1; color: rgb(187 247 208 / var(--tw-text-opacity)); } -.dark\:text-primary:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(79 70 229 / var(--tw-text-opacity)); -} - -.dark\:text-red-100:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(254 226 226 / var(--tw-text-opacity)); -} - .dark\:text-red-200:is(.dark *) { --tw-text-opacity: 1; color: rgb(254 202 202 / var(--tw-text-opacity)); @@ -3686,11 +3334,6 @@ select { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.dark\:text-yellow-100:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(254 249 195 / var(--tw-text-opacity)); -} - .dark\:text-yellow-200:is(.dark *) { --tw-text-opacity: 1; color: rgb(254 240 138 / var(--tw-text-opacity)); @@ -3745,10 +3388,6 @@ select { background-color: rgb(22 163 74 / var(--tw-bg-opacity)); } -.dark\:hover\:bg-primary\/20:hover:is(.dark *) { - background-color: rgb(79 70 229 / 0.2); -} - .dark\:hover\:bg-red-600:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); diff --git a/staticfiles/css/inline-edit.css b/staticfiles/css/inline-edit.css new file mode 100644 index 00000000..a0b77294 --- /dev/null +++ b/staticfiles/css/inline-edit.css @@ -0,0 +1,187 @@ +/* Inline editing styles */ +.editable-container { + position: relative; +} + +[data-editable] { + position: relative; + padding: 0.25rem; + border-radius: 0.25rem; + transition: background-color 0.2s; +} + +[data-editable]:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.dark [data-editable]:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +[data-edit-button] { + opacity: 0; + position: absolute; + right: 0.5rem; + top: 0.5rem; + transition: opacity 0.2s; + padding: 0.5rem; + border-radius: 0.375rem; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.dark [data-edit-button] { + background-color: rgba(31, 41, 55, 0.9); +} + +.editable-container:hover [data-edit-button] { + opacity: 1; +} + +.form-input, .form-textarea, .form-select { + width: 100%; + padding: 0.5rem; + border: 1px solid #e2e8f0; + border-radius: 0.375rem; + background-color: white; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.dark .form-input, .dark .form-textarea, .dark .form-select { + background-color: #1f2937; + border-color: #374151; + color: white; +} + +.form-input:focus, .form-textarea:focus, .form-select:focus { + outline: none; + border-color: #4f46e5; + box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); +} + +.dark .form-input:focus, .dark .form-textarea:focus, .dark .form-select:focus { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +/* Notifications */ +.notification { + position: fixed; + bottom: 1rem; + right: 1rem; + padding: 1rem; + border-radius: 0.5rem; + color: white; + max-width: 24rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + z-index: 50; + animation: slide-in 0.3s ease-out; +} + +.notification-success { + background-color: #059669; +} + +.dark .notification-success { + background-color: #047857; +} + +.notification-error { + background-color: #dc2626; +} + +.dark .notification-error { + background-color: #b91c1c; +} + +.notification-info { + background-color: #3b82f6; +} + +.dark .notification-info { + background-color: #2563eb; +} + +@keyframes slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Add/Edit Form Styles */ +.form-section { + @apply space-y-6; +} + +.form-group { + @apply space-y-2; +} + +.form-label { + @apply block text-sm font-medium text-gray-700 dark:text-gray-300; +} + +.form-error { + @apply mt-1 text-sm text-red-600 dark:text-red-400; +} + +.form-help { + @apply mt-1 text-sm text-gray-500 dark:text-gray-400; +} + +/* Button Styles */ +.btn { + @apply inline-flex items-center justify-center px-4 py-2 font-medium transition-colors rounded-lg; +} + +.btn-primary { + @apply text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600; +} + +.btn-secondary { + @apply text-gray-700 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500; +} + +.btn-danger { + @apply text-white bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600; +} + +/* Status Badges */ +.status-badge { + @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; +} + +.status-operating { + @apply text-green-800 bg-green-100 dark:bg-green-900 dark:text-green-200; +} + +.status-closed { + @apply text-red-800 bg-red-100 dark:bg-red-900 dark:text-red-200; +} + +.status-construction { + @apply text-yellow-800 bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-200; +} + +/* Navigation Links */ +.nav-link { + @apply flex items-center px-3 py-2 text-gray-700 transition-colors rounded-lg dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700; +} + +.nav-link i { + @apply mr-2; +} + +/* Menu Items */ +.menu-item { + @apply flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700; +} + +.menu-item i { + @apply mr-3; +} diff --git a/staticfiles/css/tailwind.css b/staticfiles/css/tailwind.css deleted file mode 100644 index 680a00cc..00000000 --- a/staticfiles/css/tailwind.css +++ /dev/null @@ -1 +0,0 @@ -/*! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:Poppins,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:3em;margin-top:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.6em;margin-top:1.6em;padding-inline-start:1em;quotes:"\201C""\201D""\2018""\2019"}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-weight:800;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-weight:700;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-weight:600;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-weight:500;padding-inline-end:.375em;padding-bottom:.1875em;padding-top:.1875em;padding-inline-start:.375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;color:var(--tw-prose-pre-code);font-size:.875em;font-weight:400;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;overflow-x:auto;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-top:.8571429em;padding-inline-start:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:initial;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.7142857;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em;vertical-align:bottom}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:initial}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-kbd:#111827;--tw-prose-kbd-shadows:17 24 39;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-top:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.form-input,.form-multiselect,.form-select,.form-textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem;--tw-shadow:0 0 #0000}.form-input:focus,.form-multiselect:focus,.form-select:focus,.form-textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.form-input::-moz-placeholder,.form-textarea::-moz-placeholder{color:#6b7280;opacity:1}.form-input::placeholder,.form-textarea::placeholder{color:#6b7280;opacity:1}.form-input::-webkit-datetime-edit-fields-wrapper{padding:0}.form-input::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}.form-input::-webkit-datetime-edit{display:inline-flex}.form-input::-webkit-datetime-edit,.form-input::-webkit-datetime-edit-day-field,.form-input::-webkit-datetime-edit-hour-field,.form-input::-webkit-datetime-edit-meridiem-field,.form-input::-webkit-datetime-edit-millisecond-field,.form-input::-webkit-datetime-edit-minute-field,.form-input::-webkit-datetime-edit-month-field,.form-input::-webkit-datetime-edit-second-field,.form-input::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}.form-select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}.form-select:where([size]:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}.aspect-h-9{--tw-aspect-h:9}.aspect-w-16{padding-bottom:calc(var(--tw-aspect-h)/var(--tw-aspect-w)*100%);position:relative;--tw-aspect-w:16}.aspect-w-16>*{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}.auth-card{background-color:#ffffffe6;border-color:#e5e7eb80;border-radius:1rem;border-width:1px;margin-left:auto;margin-right:auto;max-width:28rem;padding:2rem;width:100%;--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);--tw-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.auth-card:is(.dark *){background-color:#1f2937e6;border-color:#37415180}.auth-title{background-image:linear-gradient(to right,var(--tw-gradient-stops));margin-bottom:2rem;--tw-gradient-from:#4f46e5 var(--tw-gradient-from-position);--tw-gradient-to:#4f46e500 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);--tw-gradient-to:#e11d48 var(--tw-gradient-to-position);-webkit-background-clip:text;background-clip:text;color:#0000;font-size:1.5rem;font-weight:700;line-height:2rem}.auth-divider,.auth-title{text-align:center}.auth-divider{margin-bottom:1.5rem;margin-top:1.5rem;position:relative}.auth-divider:after,.auth-divider:before{border-top-width:1px;position:absolute;top:50%;width:33.333333%;--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity));--tw-content:"";content:var(--tw-content)}.auth-divider:is(.dark *):after,.auth-divider:is(.dark *):before{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.auth-divider:before{left:0}.auth-divider:after{right:0}.auth-divider span{background-color:#ffffffe6;font-size:.875rem;line-height:1.25rem;padding-left:1rem;padding-right:1rem;--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.auth-divider span:is(.dark *){background-color:#1f2937e6;--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.btn-social{align-items:center;border-radius:.5rem;border-width:1px;display:flex;justify-content:center;margin-bottom:.75rem;width:100%;--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-size:.875rem;font-weight:500;line-height:1.25rem;padding:.75rem 1.5rem;--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity));--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.btn-social,.btn-social:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.btn-social:hover{--tw-scale-x:1.02;--tw-scale-y:1.02;--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.btn-social:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:#4f46e580;--tw-ring-offset-width:2px}.btn-social:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.btn-social:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.btn-discord{border-color:#0000;--tw-bg-opacity:1;background-color:rgb(88 101 242/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity));--tw-shadow-color:#5865f233;--tw-shadow:var(--tw-shadow-colored)}.btn-discord:hover{--tw-bg-opacity:1;background-color:rgb(71 82 196/var(--tw-bg-opacity))}.btn-google{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity));--tw-shadow-color:#e5e7eb80;--tw-shadow:var(--tw-shadow-colored)}.btn-google:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.btn-google:is(.dark *){--tw-shadow-color:#11182780;--tw-shadow:var(--tw-shadow-colored)}.static{position:static}.absolute{position:absolute}.relative{position:relative}.right-3{right:.75rem}.top-1\/2{top:50%}.top-2\.5{top:.625rem}.col-span-2{grid-column:span 2/span 2}.col-span-full{grid-column:1/-1}.mx-8{margin-left:2rem;margin-right:2rem}.mx-auto{margin-left:auto;margin-right:auto}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.mb-1{margin-bottom:.25rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-auto{margin-top:auto}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-24{height:6rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-8{height:2rem}.min-h-\[calc\(100vh-16rem\)\]{min-height:calc(100vh - 16rem)}.min-h-screen{min-height:100vh}.w-1\/3{width:33.333333%}.w-24{width:6rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-8{width:2rem}.w-full{width:100%}.max-w-3xl{max-width:48rem}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.75rem*var(--tw-space-x-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1.5rem*var(--tw-space-x-reverse))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(2rem*var(--tw-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-200\/50{border-color:#e5e7eb80}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-green-200{--tw-border-opacity:1;border-color:rgb(187 247 208/var(--tw-border-opacity))}.border-green-500{--tw-border-opacity:1;border-color:rgb(34 197 94/var(--tw-border-opacity))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity))}.border-transparent{border-color:#0000}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity))}.bg-\[\#5865F2\]{--tw-bg-opacity:1;background-color:rgb(88 101 242/var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity))}.bg-blue-100\/90{background-color:#dbeafee6}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity))}.bg-green-100\/90{background-color:#dcfce7e6}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-red-100\/90{background-color:#fee2e2e6}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/70{background-color:#ffffffb3}.bg-white\/90{background-color:#ffffffe6}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity))}.bg-yellow-100\/90{background-color:#fef9c3e6}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-primary{--tw-gradient-from:#4f46e5 var(--tw-gradient-from-position);--tw-gradient-to:#4f46e500 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-white{--tw-gradient-from:#fff var(--tw-gradient-from-position);--tw-gradient-to:#fff0 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.via-blue-50{--tw-gradient-to:#eff6ff00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#eff6ff var(--tw-gradient-via-position),var(--tw-gradient-to)}.to-indigo-50{--tw-gradient-to:#eef2ff var(--tw-gradient-to-position)}.to-secondary{--tw-gradient-to:#e11d48 var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.object-cover{-o-object-fit:cover;object-fit:cover}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-16{padding-bottom:4rem;padding-top:4rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-2\.5{padding-bottom:.625rem;padding-top:.625rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pb-4{padding-bottom:1rem}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity))}.text-primary{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity))}.text-transparent{color:#0000}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity))}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-gray-200\/50{--tw-shadow-color:#e5e7eb80;--tw-shadow:var(--tw-shadow-colored)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-primary\/20{--tw-ring-color:#4f46e533}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(16px)}.backdrop-blur-lg,.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.content-\[\'\'\]{--tw-content:"";content:var(--tw-content)}.dark\:prose-invert:is(.dark *){--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-kbd:var(--tw-prose-invert-kbd);--tw-prose-kbd-shadows:var(--tw-prose-invert-kbd-shadows);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}.first\:rounded-t-lg:first-child{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.last\:mb-0:last-child{margin-bottom:0}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:-translate-y-1:hover,.hover\:translate-x-2:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:translate-x-2:hover{--tw-translate-x:0.5rem}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:scale-105:hover,.hover\:scale-\[1\.02\]:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-\[1\.02\]:hover{--tw-scale-x:1.02;--tw-scale-y:1.02}.hover\:border-primary\/20:hover{border-color:#4f46e533}.hover\:bg-\[\#4752C4\]:hover{--tw-bg-opacity:1;background-color:rgb(71 82 196/var(--tw-bg-opacity))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:bg-primary\/10:hover{background-color:#4f46e51a}.hover\:from-primary\/90:hover{--tw-gradient-from:#4f46e5e6 var(--tw-gradient-from-position);--tw-gradient-to:#4f46e500 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-secondary\/90:hover{--tw-gradient-to:#e11d48e6 var(--tw-gradient-to-position)}.hover\:text-blue-500:hover{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.hover\:text-blue-700:hover{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.hover\:text-primary:hover{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.hover\:text-primary\/80:hover{color:#4f46e5cc}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity))}.focus\:border-primary:focus{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity))}.focus\:underline:focus{text-decoration-line:underline}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity))}.focus\:ring-primary\/50:focus{--tw-ring-color:#4f46e580}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.dark\:inline:is(.dark *){display:inline}.dark\:hidden:is(.dark *){display:none}.dark\:border-gray-600:is(.dark *){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark\:border-gray-600\/50:is(.dark *){border-color:#4b556380}.dark\:border-gray-700:is(.dark *){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.dark\:border-gray-700\/50:is(.dark *){border-color:#37415180}.dark\:bg-blue-400\/30:is(.dark *){background-color:#60a5fa4d}.dark\:bg-blue-800\/30:is(.dark *){background-color:#1e40af4d}.dark\:bg-gray-600:is(.dark *){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.dark\:bg-gray-700:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:bg-gray-700\/50:is(.dark *){background-color:#37415180}.dark\:bg-gray-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:bg-gray-800\/70:is(.dark *){background-color:#1f2937b3}.dark\:bg-gray-800\/90:is(.dark *){background-color:#1f2937e6}.dark\:bg-green-800\/30:is(.dark *){background-color:#1665344d}.dark\:bg-red-800\/30:is(.dark *){background-color:#991b1b4d}.dark\:bg-yellow-400\/30:is(.dark *){background-color:#facc154d}.dark\:bg-yellow-800\/30:is(.dark *){background-color:#854d0e4d}.dark\:from-gray-950:is(.dark *){--tw-gradient-from:#030712 var(--tw-gradient-from-position);--tw-gradient-to:#03071200 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.dark\:via-indigo-950:is(.dark *){--tw-gradient-to:#1e1b4b00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#1e1b4b var(--tw-gradient-via-position),var(--tw-gradient-to)}.dark\:to-purple-950:is(.dark *){--tw-gradient-to:#3b0764 var(--tw-gradient-to-position)}.dark\:text-blue-100:is(.dark *){--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity))}.dark\:text-blue-200:is(.dark *){--tw-text-opacity:1;color:rgb(191 219 254/var(--tw-text-opacity))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark\:text-green-100:is(.dark *){--tw-text-opacity:1;color:rgb(220 252 231/var(--tw-text-opacity))}.dark\:text-red-100:is(.dark *){--tw-text-opacity:1;color:rgb(254 226 226/var(--tw-text-opacity))}.dark\:text-white:is(.dark *){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.dark\:text-yellow-100:is(.dark *){--tw-text-opacity:1;color:rgb(254 249 195/var(--tw-text-opacity))}.dark\:text-yellow-200:is(.dark *){--tw-text-opacity:1;color:rgb(254 240 138/var(--tw-text-opacity))}.dark\:text-yellow-300:is(.dark *){--tw-text-opacity:1;color:rgb(253 224 71/var(--tw-text-opacity))}.dark\:ring-1:is(.dark *){--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.dark\:ring-blue-400\/30:is(.dark *){--tw-ring-color:#60a5fa4d}.dark\:ring-yellow-400\/30:is(.dark *){--tw-ring-color:#facc154d}.dark\:hover\:bg-gray-700:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark\:hover\:bg-primary\/20:hover:is(.dark *){background-color:#4f46e533}.dark\:hover\:text-blue-300:hover:is(.dark *){--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity))}.dark\:hover\:text-primary:hover:is(.dark *){--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:768px){.md\:mt-0{margin-top:0}.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:text-2xl{font-size:1.5rem;line-height:2rem}.md\:text-5xl{font-size:3rem;line-height:1}}@media (min-width:1024px){.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:text-6xl{font-size:3.75rem;line-height:1}} \ No newline at end of file diff --git a/staticfiles/js/inline-edit.js b/staticfiles/js/inline-edit.js new file mode 100644 index 00000000..65e2dca3 --- /dev/null +++ b/staticfiles/js/inline-edit.js @@ -0,0 +1,262 @@ +document.addEventListener('DOMContentLoaded', function() { + // Handle edit button clicks + document.querySelectorAll('[data-edit-button]').forEach(button => { + button.addEventListener('click', function() { + const contentId = this.dataset.contentId; + const contentType = this.dataset.contentType; + const editableFields = document.querySelectorAll(`[data-editable][data-content-id="${contentId}"]`); + + // Toggle edit mode + editableFields.forEach(field => { + const currentValue = field.textContent.trim(); + const fieldName = field.dataset.fieldName; + const fieldType = field.dataset.fieldType || 'text'; + + // Create input field + let input; + if (fieldType === 'textarea') { + input = document.createElement('textarea'); + input.value = currentValue; + input.rows = 4; + } else if (fieldType === 'select') { + input = document.createElement('select'); + // Get options from data attribute + const options = JSON.parse(field.dataset.options || '[]'); + options.forEach(option => { + const optionEl = document.createElement('option'); + optionEl.value = option.value; + optionEl.textContent = option.label; + optionEl.selected = option.value === currentValue; + input.appendChild(optionEl); + }); + } else if (fieldType === 'date') { + input = document.createElement('input'); + input.type = 'date'; + input.value = currentValue; + } else if (fieldType === 'number') { + input = document.createElement('input'); + input.type = 'number'; + input.value = currentValue; + if (field.dataset.min) input.min = field.dataset.min; + if (field.dataset.max) input.max = field.dataset.max; + if (field.dataset.step) input.step = field.dataset.step; + } else { + input = document.createElement('input'); + input.type = fieldType; + input.value = currentValue; + } + + input.className = 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'; + input.dataset.originalValue = currentValue; + input.dataset.fieldName = fieldName; + + // Replace content with input + field.textContent = ''; + field.appendChild(input); + }); + + // Show save/cancel buttons + const actionButtons = document.createElement('div'); + actionButtons.className = 'flex gap-2 mt-2'; + actionButtons.innerHTML = ` + + + ${this.dataset.requireReason ? ` +
+ + +
+ ` : ''} + `; + + const container = editableFields[0].closest('.editable-container'); + container.appendChild(actionButtons); + + // Hide edit button while editing + this.style.display = 'none'; + }); + }); + + // Handle form submissions + document.querySelectorAll('form[data-submit-type]').forEach(form => { + form.addEventListener('submit', function(e) { + e.preventDefault(); + + const submitType = this.dataset.submitType; + const formData = new FormData(this); + const data = {}; + + formData.forEach((value, key) => { + data[key] = value; + }); + + // Get CSRF token from meta tag + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + + // Submit form + fetch(this.action, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ + submission_type: submitType, + ...data + }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + showNotification(data.message, 'success'); + if (data.redirect_url) { + window.location.href = data.redirect_url; + } + } else { + showNotification(data.message, 'error'); + } + }) + .catch(error => { + showNotification('An error occurred while submitting the form.', 'error'); + console.error('Error:', error); + }); + }); + }); + + // Handle save button clicks using event delegation + document.addEventListener('click', function(e) { + if (e.target.matches('[data-save-button]')) { + const container = e.target.closest('.editable-container'); + const contentId = container.querySelector('[data-editable]').dataset.contentId; + const contentType = container.querySelector('[data-edit-button]').dataset.contentType; + const editableFields = container.querySelectorAll('[data-editable]'); + + // Collect changes + const changes = {}; + editableFields.forEach(field => { + const input = field.querySelector('input, textarea, select'); + if (input && input.value !== input.dataset.originalValue) { + changes[input.dataset.fieldName] = input.value; + } + }); + + // If no changes, just cancel + if (Object.keys(changes).length === 0) { + cancelEdit(container); + return; + } + + // Get reason and source if required + const reasonInput = container.querySelector('[data-reason-input]'); + const sourceInput = container.querySelector('[data-source-input]'); + const reason = reasonInput ? reasonInput.value : ''; + const source = sourceInput ? sourceInput.value : ''; + + // Validate reason if required + if (reasonInput && !reason) { + alert('Please provide a reason for your changes.'); + return; + } + + // Get CSRF token from meta tag + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + + // Submit changes + fetch(window.location.pathname, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ + content_type: contentType, + content_id: contentId, + changes, + reason, + source + }) + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + if (data.auto_approved) { + // Update the display immediately + Object.entries(changes).forEach(([field, value]) => { + const element = container.querySelector(`[data-editable][data-field-name="${field}"]`); + if (element) { + element.textContent = value; + } + }); + } + showNotification(data.message, 'success'); + if (data.redirect_url) { + window.location.href = data.redirect_url; + } + } else { + showNotification(data.message, 'error'); + } + cancelEdit(container); + }) + .catch(error => { + showNotification('An error occurred while saving changes.', 'error'); + console.error('Error:', error); + cancelEdit(container); + }); + } + }); + + // Handle cancel button clicks using event delegation + document.addEventListener('click', function(e) { + if (e.target.matches('[data-cancel-button]')) { + const container = e.target.closest('.editable-container'); + cancelEdit(container); + } + }); +}); + +function cancelEdit(container) { + // Restore original content + container.querySelectorAll('[data-editable]').forEach(field => { + const input = field.querySelector('input, textarea, select'); + if (input) { + field.textContent = input.dataset.originalValue; + } + }); + + // Remove action buttons + const actionButtons = container.querySelector('.flex.gap-2'); + if (actionButtons) { + actionButtons.remove(); + } + + // Show edit button + const editButton = container.querySelector('[data-edit-button]'); + if (editButton) { + editButton.style.display = ''; + } +} + +function showNotification(message, type = 'info') { + const notification = document.createElement('div'); + notification.className = `fixed bottom-4 right-4 p-4 rounded-lg shadow-lg text-white ${ + type === 'success' ? 'bg-green-600 dark:bg-green-500' : + type === 'error' ? 'bg-red-600 dark:bg-red-500' : + 'bg-blue-600 dark:bg-blue-500' + }`; + notification.textContent = message; + + document.body.appendChild(notification); + + // Remove after 5 seconds + setTimeout(() => { + notification.remove(); + }, 5000); +} diff --git a/templates/environment_and_settings.html b/templates/environment_and_settings.html new file mode 100644 index 00000000..ac9258b8 --- /dev/null +++ b/templates/environment_and_settings.html @@ -0,0 +1,37 @@ + + + + + + Django Environment and Settings + + +

Django Environment Variables

+ + + + + + {% for key, value in env_vars.items %} + + + + + {% endfor %} +
VariableValue
{{ key }}{{ value }}
+ +

Django Settings

+ + + + + + {% for key, value in settings_vars.items %} + + + + + {% endfor %} +
SettingValue
{{ key }}{{ value }}
+ + diff --git a/templates/parks/park_form.html b/templates/parks/park_form.html index 35beb6ea..bff795b8 100644 --- a/templates/parks/park_form.html +++ b/templates/parks/park_form.html @@ -12,57 +12,89 @@
{% csrf_token %} - {% for field in form %} + + {{ form.country }} + {{ form.region }} + {{ form.city }} + +
-
+ + +
+ +
+ {{ form.country_name }} +
+ {% if form.country_name.errors %} +
+ {{ form.country_name.errors }} +
+ {% endif %} +
+ +
+ +
+ {{ form.region_name }} +
+ {% if form.region_name.errors %} +
+ {{ form.region_name.errors }} +
+ {% endif %} +
+ +
+ +
+ {{ form.city_name }} +
+ {% if form.city_name.errors %} +
+ {{ form.city_name.errors }} +
+ {% endif %} +
+ + + {% for field in form %} + {% if field.name not in 'name,country,region,city,country_name,region_name,city_name' %} +
+ +
+ {{ field }} +
+ {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} + {% if field.errors %} +
+ {{ field.errors }} +
+ {% endif %} +
+ {% endif %} {% endfor %} {% if not user.role == 'MODERATOR' and not user.role == 'ADMIN' and not user.role == 'SUPERUSER' %} @@ -104,3 +136,100 @@ {% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block extra_js %} + + +{% endblock %} diff --git a/templates/parks/park_list.html b/templates/parks/park_list.html index 5a2e759b..50f5c05d 100644 --- a/templates/parks/park_list.html +++ b/templates/parks/park_list.html @@ -18,7 +18,7 @@
@@ -30,15 +30,10 @@
- +
@@ -62,3 +57,54 @@
{% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block extra_js %} + + +{% endblock %} diff --git a/templates/parks/partials/city_dropdown_list.html b/templates/parks/partials/city_dropdown_list.html new file mode 100644 index 00000000..ce01b288 --- /dev/null +++ b/templates/parks/partials/city_dropdown_list.html @@ -0,0 +1,4 @@ + +{% for city in cities %} + +{% endfor %} diff --git a/templates/parks/partials/region_dropdown_list.html b/templates/parks/partials/region_dropdown_list.html new file mode 100644 index 00000000..caaa19a4 --- /dev/null +++ b/templates/parks/partials/region_dropdown_list.html @@ -0,0 +1,4 @@ + +{% for region in regions %} + +{% endfor %} diff --git a/thrillwiki/__pycache__/__init__.cpython-311.pyc b/thrillwiki/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 7d889f5b..00000000 Binary files a/thrillwiki/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/thrillwiki/__pycache__/settings.cpython-311.pyc b/thrillwiki/__pycache__/settings.cpython-311.pyc deleted file mode 100644 index 06cb7202..00000000 Binary files a/thrillwiki/__pycache__/settings.cpython-311.pyc and /dev/null differ diff --git a/thrillwiki/__pycache__/settings.cpython-312.pyc b/thrillwiki/__pycache__/settings.cpython-312.pyc index dff70412..7ad678ed 100644 Binary files a/thrillwiki/__pycache__/settings.cpython-312.pyc and b/thrillwiki/__pycache__/settings.cpython-312.pyc differ diff --git a/thrillwiki/__pycache__/urls.cpython-311.pyc b/thrillwiki/__pycache__/urls.cpython-311.pyc deleted file mode 100644 index 991d5fb8..00000000 Binary files a/thrillwiki/__pycache__/urls.cpython-311.pyc and /dev/null differ diff --git a/thrillwiki/__pycache__/urls.cpython-312.pyc b/thrillwiki/__pycache__/urls.cpython-312.pyc index fd732fdc..27683794 100644 Binary files a/thrillwiki/__pycache__/urls.cpython-312.pyc and b/thrillwiki/__pycache__/urls.cpython-312.pyc differ diff --git a/thrillwiki/__pycache__/views.cpython-311.pyc b/thrillwiki/__pycache__/views.cpython-311.pyc deleted file mode 100644 index 3a772cfb..00000000 Binary files a/thrillwiki/__pycache__/views.cpython-311.pyc and /dev/null differ diff --git a/thrillwiki/__pycache__/views.cpython-312.pyc b/thrillwiki/__pycache__/views.cpython-312.pyc index 45c96280..a666c36c 100644 Binary files a/thrillwiki/__pycache__/views.cpython-312.pyc and b/thrillwiki/__pycache__/views.cpython-312.pyc differ diff --git a/thrillwiki/__pycache__/wsgi.cpython-311.pyc b/thrillwiki/__pycache__/wsgi.cpython-311.pyc deleted file mode 100644 index 29e969a1..00000000 Binary files a/thrillwiki/__pycache__/wsgi.cpython-311.pyc and /dev/null differ diff --git a/thrillwiki/settings.py b/thrillwiki/settings.py index c6ffcc40..5d03af4b 100644 --- a/thrillwiki/settings.py +++ b/thrillwiki/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = [ 'django_htmx', 'whitenoise', 'django_tailwind_cli', + 'cities_light', # Local apps 'core', @@ -51,6 +52,11 @@ INSTALLED_APPS = [ 'moderation', ] +# Cities Light settings +CITIES_LIGHT_TRANSLATION_LANGUAGES = ['en'] +CITIES_LIGHT_INCLUDE_COUNTRIES = ['US', 'CA', 'GB', 'FR', 'DE', 'ES', 'IT', 'JP', 'CN', 'AU'] +CITIES_LIGHT_INCLUDE_CITY_TYPES = ['PPL', 'PPLA', 'PPLA2', 'PPLA3', 'PPLA4', 'PPLC', 'PPLG', 'PPLL', 'PPLR', 'PPLS'] + MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.security.SecurityMiddleware', @@ -113,7 +119,7 @@ CACHES = { } } -CACHE_MIDDLEWARE_SECONDS = 300 # 5 minutes +CACHE_MIDDLEWARE_SECONDS = 1 # 5 minutes CACHE_MIDDLEWARE_KEY_PREFIX = 'thrillwiki' # Password validation @@ -139,10 +145,8 @@ USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) -STATIC_URL = '/static/' -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static'), -] +STATIC_URL = 'static/' +STATICFILES_DIRS = [BASE_DIR / "static"] STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # Media files @@ -209,9 +213,9 @@ FORWARD_EMAIL_BASE_URL = 'https://api.forwardemail.net' AUTH_USER_MODEL = 'accounts.User' # Tailwind configuration -TAILWIND_CLI_CONFIG_FILE = os.path.join(BASE_DIR, 'tailwind.config.js') -TAILWIND_CLI_SRC_CSS = os.path.join(BASE_DIR, 'assets/css/src/input.css') -TAILWIND_CLI_DIST_CSS = os.path.join(BASE_DIR, 'static/css/tailwind.css') +TAILWIND_CLI_CONFIG_FILE = os.path.join(BASE_DIR, "tailwind.config.js") +TAILWIND_CLI_SRC_CSS = os.path.join(BASE_DIR, "static/css/src/input.css") +TAILWIND_CLI_DIST_CSS = os.path.join(BASE_DIR, "static/css/tailwind.css") # Cloudflare Turnstile settings TURNSTILE_SITE_KEY = '0x4AAAAAAAyqVp3RjccrC9Kz' diff --git a/thrillwiki/urls.py b/thrillwiki/urls.py index 9d4241ce..6a02d31f 100644 --- a/thrillwiki/urls.py +++ b/thrillwiki/urls.py @@ -5,6 +5,7 @@ from django.conf.urls.static import static from accounts import views as accounts_views from django.views.generic import TemplateView from .views import HomeView, SearchView +from . import views urlpatterns = [ path('admin/', admin.site.urls), @@ -40,6 +41,7 @@ urlpatterns = [ # Moderation URLs - placed after other URLs but before static/media serving path('moderation/', include('moderation.urls', namespace='moderation')), + path('env-settings/', views***REMOVED***ironment_and_settings_view, name='environment_and_settings'), ] # Serve static files in development diff --git a/thrillwiki/views.py b/thrillwiki/views.py index 85805c1d..1a431453 100644 --- a/thrillwiki/views.py +++ b/thrillwiki/views.py @@ -4,6 +4,9 @@ from django.db.models import Count, Q from parks.models import Park from rides.models import Ride from companies.models import Company, Manufacturer +from django.conf import settings +import os + def handler404(request, exception): return render(request, '404.html', status=404) @@ -73,3 +76,14 @@ class SearchView(TemplateView): ).prefetch_related('rides')[:10] return context +def environment_and_settings_view(request): + # Get all environment variables + env_vars = dict(os***REMOVED***iron) + + # Get all Django settings as a dictionary + settings_vars = {setting: getattr(settings, setting) for setting in dir(settings) if setting.isupper()} + + return render(request, 'environment_and_settings.html', { + 'env_vars': env_vars, + 'settings_vars': settings_vars + }) \ No newline at end of file