From 715e284b3eef5c6b0f7f83461c563eb6e47f8290 Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:01:28 -0400 Subject: [PATCH] Removed VueJS frontend and dramatically enhanced API --- api_endpoints_curl_commands.sh | 649 ++ backend/apps/api/apps.py | 4 + backend/apps/api/v1/maps/views.py | 558 +- backend/apps/api/v1/serializers/__init__.py | 96 +- backend/apps/api/v1/serializers/maps.py | 408 ++ backend/apps/api/v1/serializers/stats.py | 155 + backend/apps/api/v1/signals.py | 95 + backend/apps/api/v1/urls.py | 4 + backend/apps/api/v1/views/auth.py | 95 +- backend/apps/api/v1/views/health.py | 45 +- backend/apps/api/v1/views/stats.py | 358 ++ backend/apps/core/middleware/__init__.py | 2 + backend/apps/core/middleware/analytics.py | 7 +- backend/apps/core/middleware/nextjs.py | 48 + .../core/middleware/performance_middleware.py | 7 +- .../core/services/enhanced_cache_service.py | 17 +- backend/config/django/base.py | 19 +- backend/config/django/local.py | 1 + cline_docs/activeContext.md | 177 +- docs/frontend-connection-guide.md | 1537 +++++ frontend/.editorconfig | 8 - frontend/.env.development | 6 - frontend/.env.production | 6 - frontend/.env.staging | 6 - frontend/.gitattributes | 3 - frontend/.gitignore | 36 - frontend/.nvmrc | 1 - frontend/.prettierrc.json | 6 - frontend/README.md | 384 -- frontend/bun.lock | 1272 ---- frontend/components.d.ts | 216 - frontend/components.json | 20 - frontend/e2e/tsconfig.json | 4 - frontend/e2e/vue.spec.ts | 8 - frontend/env.d.ts | 1 - frontend/eslint.config.ts | 36 - frontend/index.html | 13 - frontend/package.json | 76 - frontend/playwright.config.ts | 110 - frontend/pnpm-lock.yaml | 5520 ----------------- frontend/pnpm-workspace.yaml | 2 - frontend/postcss.config.js | 6 - frontend/public/favicon.ico | Bin 4286 -> 0 bytes frontend/src/App.vue | 317 - frontend/src/__tests__/App.spec.ts | 11 - frontend/src/components/AppSidebar.vue | 387 -- frontend/src/components/auth/AuthManager.vue | 236 - frontend/src/components/auth/AuthModal.vue | 103 - .../components/auth/ForgotPasswordModal.vue | 175 - frontend/src/components/auth/LoginModal.vue | 237 - frontend/src/components/auth/SignupModal.vue | 335 - frontend/src/components/button/button.css | 87 - frontend/src/components/button/index.tsx | 137 - frontend/src/components/entity/AuthPrompt.vue | 173 - .../entity/EntitySuggestionCard.vue | 185 - .../entity/EntitySuggestionManager.vue | 235 - .../entity/EntitySuggestionModal.vue | 214 - frontend/src/components/entity/index.ts | 7 - .../components/filters/ActiveFilterChip.vue | 125 - .../components/filters/DateRangeFilter.vue | 408 -- .../src/components/filters/FilterSection.vue | 71 - .../src/components/filters/PresetItem.vue | 209 - .../src/components/filters/RangeFilter.vue | 419 -- .../components/filters/RideFilterSidebar.vue | 935 --- .../components/filters/SavePresetDialog.vue | 390 -- .../src/components/filters/SearchFilter.vue | 343 - .../components/filters/SearchableSelect.vue | 476 -- .../src/components/filters/SelectFilter.vue | 348 -- frontend/src/components/icon/icon.css | 238 - frontend/src/components/icon/index.tsx | 38 - frontend/src/components/icons/DiscordIcon.vue | 7 - frontend/src/components/icons/GoogleIcon.vue | 20 - frontend/src/components/layout/Navbar.vue | 259 - .../layout/PrimeThemeController.vue | 172 - .../src/components/layout/ThemeController.vue | 363 -- .../src/components/primevue/PrimeBadge.vue | 106 - .../src/components/primevue/PrimeButton.vue | 161 - .../src/components/primevue/PrimeCard.vue | 188 - .../src/components/primevue/PrimeDialog.vue | 225 - .../src/components/primevue/PrimeInput.vue | 251 - .../src/components/primevue/PrimeProgress.vue | 211 - .../src/components/primevue/PrimeSelect.vue | 296 - .../src/components/primevue/PrimeSkeleton.vue | 129 - frontend/src/components/primevue/index.ts | 46 - frontend/src/components/rides/RideCard.vue | 345 -- .../src/components/rides/RideListDisplay.vue | 486 -- frontend/src/components/state-layer/index.tsx | 19 - .../components/state-layer/state-layer.css | 46 - frontend/src/components/test/PrimeVueTest.vue | 107 - frontend/src/composables/useAuth.ts | 201 - frontend/src/composables/useRideFiltering.ts | 395 -- frontend/src/composables/useTheme.ts | 146 - frontend/src/main.ts | 142 - frontend/src/router/index.ts | 104 - frontend/src/services/api.ts | 1292 ---- frontend/src/stores/counter.ts | 12 - frontend/src/stores/parks.ts | 72 - frontend/src/stores/rideFiltering.ts | 498 -- frontend/src/stores/rides.ts | 115 - frontend/src/style.css | 152 - frontend/src/theme/primevue-theme.ts | 211 - frontend/src/types/declarations.d.ts | 164 - frontend/src/types/filters.ts | 172 - frontend/src/types/index.ts | 385 -- frontend/src/views/Error.vue | 127 - frontend/src/views/Home.vue | 710 --- frontend/src/views/NotFound.vue | 116 - frontend/src/views/RideFilteringPage.vue | 396 -- frontend/src/views/SearchResults.vue | 378 -- .../src/views/accounts/ForgotPassword.vue | 64 - frontend/src/views/accounts/Login.vue | 78 - frontend/src/views/accounts/Signup.vue | 78 - frontend/src/views/parks/ParkDetail.vue | 237 - frontend/src/views/parks/ParkList.vue | 1003 --- frontend/src/views/rides/RideDetail.vue | 267 - frontend/src/views/rides/RideList.vue | 621 -- frontend/tailwind.config.js | 34 - frontend/tsconfig.app.json | 12 - frontend/tsconfig.json | 21 - frontend/tsconfig.node.json | 19 - frontend/tsconfig.vitest.json | 11 - frontend/vite.config.ts | 57 - frontend/vitest.config.ts | 14 - 123 files changed, 4056 insertions(+), 26546 deletions(-) create mode 100755 api_endpoints_curl_commands.sh create mode 100644 backend/apps/api/v1/serializers/maps.py create mode 100644 backend/apps/api/v1/serializers/stats.py create mode 100644 backend/apps/api/v1/signals.py create mode 100644 backend/apps/api/v1/views/stats.py create mode 100644 backend/apps/core/middleware/nextjs.py create mode 100644 docs/frontend-connection-guide.md delete mode 100644 frontend/.editorconfig delete mode 100644 frontend/.env.development delete mode 100644 frontend/.env.production delete mode 100644 frontend/.env.staging delete mode 100644 frontend/.gitattributes delete mode 100644 frontend/.gitignore delete mode 100644 frontend/.nvmrc delete mode 100644 frontend/.prettierrc.json delete mode 100644 frontend/README.md delete mode 100644 frontend/bun.lock delete mode 100644 frontend/components.d.ts delete mode 100644 frontend/components.json delete mode 100644 frontend/e2e/tsconfig.json delete mode 100644 frontend/e2e/vue.spec.ts delete mode 100644 frontend/env.d.ts delete mode 100644 frontend/eslint.config.ts delete mode 100644 frontend/index.html delete mode 100644 frontend/package.json delete mode 100644 frontend/playwright.config.ts delete mode 100644 frontend/pnpm-lock.yaml delete mode 100644 frontend/pnpm-workspace.yaml delete mode 100644 frontend/postcss.config.js delete mode 100644 frontend/public/favicon.ico delete mode 100644 frontend/src/App.vue delete mode 100644 frontend/src/__tests__/App.spec.ts delete mode 100644 frontend/src/components/AppSidebar.vue delete mode 100644 frontend/src/components/auth/AuthManager.vue delete mode 100644 frontend/src/components/auth/AuthModal.vue delete mode 100644 frontend/src/components/auth/ForgotPasswordModal.vue delete mode 100644 frontend/src/components/auth/LoginModal.vue delete mode 100644 frontend/src/components/auth/SignupModal.vue delete mode 100644 frontend/src/components/button/button.css delete mode 100644 frontend/src/components/button/index.tsx delete mode 100644 frontend/src/components/entity/AuthPrompt.vue delete mode 100644 frontend/src/components/entity/EntitySuggestionCard.vue delete mode 100644 frontend/src/components/entity/EntitySuggestionManager.vue delete mode 100644 frontend/src/components/entity/EntitySuggestionModal.vue delete mode 100644 frontend/src/components/entity/index.ts delete mode 100644 frontend/src/components/filters/ActiveFilterChip.vue delete mode 100644 frontend/src/components/filters/DateRangeFilter.vue delete mode 100644 frontend/src/components/filters/FilterSection.vue delete mode 100644 frontend/src/components/filters/PresetItem.vue delete mode 100644 frontend/src/components/filters/RangeFilter.vue delete mode 100644 frontend/src/components/filters/RideFilterSidebar.vue delete mode 100644 frontend/src/components/filters/SavePresetDialog.vue delete mode 100644 frontend/src/components/filters/SearchFilter.vue delete mode 100644 frontend/src/components/filters/SearchableSelect.vue delete mode 100644 frontend/src/components/filters/SelectFilter.vue delete mode 100644 frontend/src/components/icon/icon.css delete mode 100644 frontend/src/components/icon/index.tsx delete mode 100644 frontend/src/components/icons/DiscordIcon.vue delete mode 100644 frontend/src/components/icons/GoogleIcon.vue delete mode 100644 frontend/src/components/layout/Navbar.vue delete mode 100644 frontend/src/components/layout/PrimeThemeController.vue delete mode 100644 frontend/src/components/layout/ThemeController.vue delete mode 100644 frontend/src/components/primevue/PrimeBadge.vue delete mode 100644 frontend/src/components/primevue/PrimeButton.vue delete mode 100644 frontend/src/components/primevue/PrimeCard.vue delete mode 100644 frontend/src/components/primevue/PrimeDialog.vue delete mode 100644 frontend/src/components/primevue/PrimeInput.vue delete mode 100644 frontend/src/components/primevue/PrimeProgress.vue delete mode 100644 frontend/src/components/primevue/PrimeSelect.vue delete mode 100644 frontend/src/components/primevue/PrimeSkeleton.vue delete mode 100644 frontend/src/components/primevue/index.ts delete mode 100644 frontend/src/components/rides/RideCard.vue delete mode 100644 frontend/src/components/rides/RideListDisplay.vue delete mode 100644 frontend/src/components/state-layer/index.tsx delete mode 100644 frontend/src/components/state-layer/state-layer.css delete mode 100644 frontend/src/components/test/PrimeVueTest.vue delete mode 100644 frontend/src/composables/useAuth.ts delete mode 100644 frontend/src/composables/useRideFiltering.ts delete mode 100644 frontend/src/composables/useTheme.ts delete mode 100644 frontend/src/main.ts delete mode 100644 frontend/src/router/index.ts delete mode 100644 frontend/src/services/api.ts delete mode 100644 frontend/src/stores/counter.ts delete mode 100644 frontend/src/stores/parks.ts delete mode 100644 frontend/src/stores/rideFiltering.ts delete mode 100644 frontend/src/stores/rides.ts delete mode 100644 frontend/src/style.css delete mode 100644 frontend/src/theme/primevue-theme.ts delete mode 100644 frontend/src/types/declarations.d.ts delete mode 100644 frontend/src/types/filters.ts delete mode 100644 frontend/src/types/index.ts delete mode 100644 frontend/src/views/Error.vue delete mode 100644 frontend/src/views/Home.vue delete mode 100644 frontend/src/views/NotFound.vue delete mode 100644 frontend/src/views/RideFilteringPage.vue delete mode 100644 frontend/src/views/SearchResults.vue delete mode 100644 frontend/src/views/accounts/ForgotPassword.vue delete mode 100644 frontend/src/views/accounts/Login.vue delete mode 100644 frontend/src/views/accounts/Signup.vue delete mode 100644 frontend/src/views/parks/ParkDetail.vue delete mode 100644 frontend/src/views/parks/ParkList.vue delete mode 100644 frontend/src/views/rides/RideDetail.vue delete mode 100644 frontend/src/views/rides/RideList.vue delete mode 100644 frontend/tailwind.config.js delete mode 100644 frontend/tsconfig.app.json delete mode 100644 frontend/tsconfig.json delete mode 100644 frontend/tsconfig.node.json delete mode 100644 frontend/tsconfig.vitest.json delete mode 100644 frontend/vite.config.ts delete mode 100644 frontend/vitest.config.ts diff --git a/api_endpoints_curl_commands.sh b/api_endpoints_curl_commands.sh new file mode 100755 index 00000000..9a0461c2 --- /dev/null +++ b/api_endpoints_curl_commands.sh @@ -0,0 +1,649 @@ +#!/bin/bash + +# ThrillWiki API Endpoints - Complete Curl Commands +# Generated from comprehensive URL analysis +# Base URL - adjust as needed for your environment +BASE_URL="http://localhost:8000" + +# Command line options +SKIP_AUTH=false +ONLY_AUTH=false +SKIP_DOCS=false +HELP=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --skip-auth) + SKIP_AUTH=true + shift + ;; + --only-auth) + ONLY_AUTH=true + shift + ;; + --skip-docs) + SKIP_DOCS=true + shift + ;; + --base-url) + BASE_URL="$2" + shift 2 + ;; + --help|-h) + HELP=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Show help +if [ "$HELP" = true ]; then + echo "ThrillWiki API Endpoints Test Suite" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --skip-auth Skip endpoints that require authentication" + echo " --only-auth Only test endpoints that require authentication" + echo " --skip-docs Skip API documentation endpoints (schema, swagger, redoc)" + echo " --base-url URL Set custom base URL (default: http://localhost:8000)" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0 # Test all endpoints" + echo " $0 --skip-auth # Test only public endpoints" + echo " $0 --only-auth # Test only authenticated endpoints" + echo " $0 --skip-docs --skip-auth # Test only public non-documentation endpoints" + echo " $0 --base-url https://api.example.com # Use custom base URL" + exit 0 +fi + +# Validate conflicting options +if [ "$SKIP_AUTH" = true ] && [ "$ONLY_AUTH" = true ]; then + echo "Error: --skip-auth and --only-auth cannot be used together" + exit 1 +fi + +echo "=== ThrillWiki API Endpoints Test Suite ===" +echo "Base URL: $BASE_URL" +if [ "$SKIP_AUTH" = true ]; then + echo "Mode: Public endpoints only (skipping authentication required)" +elif [ "$ONLY_AUTH" = true ]; then + echo "Mode: Authenticated endpoints only" +else + echo "Mode: All endpoints" +fi +if [ "$SKIP_DOCS" = true ]; then + echo "Skipping: API documentation endpoints" +fi +echo "" + +# Helper function to check if we should run an endpoint +should_run_endpoint() { + local requires_auth=$1 + local is_docs=$2 + + # Skip docs if requested + if [ "$SKIP_DOCS" = true ] && [ "$is_docs" = true ]; then + return 1 + fi + + # Skip auth endpoints if requested + if [ "$SKIP_AUTH" = true ] && [ "$requires_auth" = true ]; then + return 1 + fi + + # Only run auth endpoints if requested + if [ "$ONLY_AUTH" = true ] && [ "$requires_auth" = false ]; then + return 1 + fi + + return 0 +} + +# Counter for endpoint numbering +ENDPOINT_NUM=1 + +# ============================================================================ +# AUTHENTICATION ENDPOINTS (/api/v1/auth/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo "=== AUTHENTICATION ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. Login" + curl -X POST "$BASE_URL/api/v1/auth/login/" \ + -H "Content-Type: application/json" \ + -d '{"username": "testuser", "password": "testpass"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Signup" + curl -X POST "$BASE_URL/api/v1/auth/signup/" \ + -H "Content-Type: application/json" \ + -d '{"username": "newuser", "email": "test@example.com", "password": "newpass123"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Logout" + curl -X POST "$BASE_URL/api/v1/auth/logout/" \ + -H "Content-Type: application/json" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Password Reset" + curl -X POST "$BASE_URL/api/v1/auth/password/reset/" \ + -H "Content-Type: application/json" \ + -d '{"email": "user@example.com"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Social Providers" + curl -X GET "$BASE_URL/api/v1/auth/providers/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Auth Status" + curl -X GET "$BASE_URL/api/v1/auth/status/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Current User" + curl -X GET "$BASE_URL/api/v1/auth/user/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Password Change" + curl -X POST "$BASE_URL/api/v1/auth/password/change/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"old_password": "oldpass", "new_password": "newpass123"}' + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# HEALTH CHECK ENDPOINTS (/api/v1/health/) +# ============================================================================ +if should_run_endpoint false false; then + echo -e "\n\n=== HEALTH CHECK ENDPOINTS ===" + + echo "$ENDPOINT_NUM. Health Check" + curl -X GET "$BASE_URL/api/v1/health/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Simple Health" + curl -X GET "$BASE_URL/api/v1/health/simple/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Performance Metrics" + curl -X GET "$BASE_URL/api/v1/health/performance/" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# TRENDING SYSTEM ENDPOINTS (/api/v1/trending/) +# ============================================================================ +if should_run_endpoint false false; then + echo -e "\n\n=== TRENDING SYSTEM ENDPOINTS ===" + + echo "$ENDPOINT_NUM. Trending Content" + curl -X GET "$BASE_URL/api/v1/trending/content/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. New Content" + curl -X GET "$BASE_URL/api/v1/trending/new/" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# STATISTICS ENDPOINTS (/api/v1/stats/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== STATISTICS ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. Statistics" + curl -X GET "$BASE_URL/api/v1/stats/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Recalculate Statistics" + curl -X POST "$BASE_URL/api/v1/stats/recalculate/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# RANKING SYSTEM ENDPOINTS (/api/v1/rankings/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== RANKING SYSTEM ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. List Rankings" + curl -X GET "$BASE_URL/api/v1/rankings/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. List Rankings with Filters" + curl -X GET "$BASE_URL/api/v1/rankings/?category=RC&min_riders=10&ordering=rank" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ranking Detail" + curl -X GET "$BASE_URL/api/v1/rankings/ride-slug-here/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ranking History" + curl -X GET "$BASE_URL/api/v1/rankings/ride-slug-here/history/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ranking Statistics" + curl -X GET "$BASE_URL/api/v1/rankings/statistics/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ranking Comparisons" + curl -X GET "$BASE_URL/api/v1/rankings/ride-slug-here/comparisons/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Trigger Ranking Calculation" + curl -X POST "$BASE_URL/api/v1/rankings/calculate/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"category": "RC"}' + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# PARKS API ENDPOINTS (/api/v1/parks/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== PARKS API ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. List Parks" + curl -X GET "$BASE_URL/api/v1/parks/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park Filter Options" + curl -X GET "$BASE_URL/api/v1/parks/filter-options/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park Company Search" + curl -X GET "$BASE_URL/api/v1/parks/search/companies/?q=disney" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park Search Suggestions" + curl -X GET "$BASE_URL/api/v1/parks/search-suggestions/?q=magic" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park Detail" + curl -X GET "$BASE_URL/api/v1/parks/1/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. List Park Photos" + curl -X GET "$BASE_URL/api/v1/parks/1/photos/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park Photo Detail" + curl -X GET "$BASE_URL/api/v1/parks/1/photos/1/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Create Park" + curl -X POST "$BASE_URL/api/v1/parks/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "Test Park", "location": "Test City"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Park" + curl -X PUT "$BASE_URL/api/v1/parks/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "Updated Park Name"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Park" + curl -X DELETE "$BASE_URL/api/v1/parks/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Create Park Photo" + curl -X POST "$BASE_URL/api/v1/parks/1/photos/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -F "image=@/path/to/photo.jpg" \ + -F "caption=Test photo" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Park Photo" + curl -X PUT "$BASE_URL/api/v1/parks/1/photos/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"caption": "Updated caption"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Park Photo" + curl -X DELETE "$BASE_URL/api/v1/parks/1/photos/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# RIDES API ENDPOINTS (/api/v1/rides/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== RIDES API ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. List Rides" + curl -X GET "$BASE_URL/api/v1/rides/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Filter Options" + curl -X GET "$BASE_URL/api/v1/rides/filter-options/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Company Search" + curl -X GET "$BASE_URL/api/v1/rides/search/companies/?q=intamin" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Model Search" + curl -X GET "$BASE_URL/api/v1/rides/search/ride-models/?q=giga" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Search Suggestions" + curl -X GET "$BASE_URL/api/v1/rides/search-suggestions/?q=millennium" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Detail" + curl -X GET "$BASE_URL/api/v1/rides/1/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. List Ride Photos" + curl -X GET "$BASE_URL/api/v1/rides/1/photos/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride Photo Detail" + curl -X GET "$BASE_URL/api/v1/rides/1/photos/1/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Create Ride" + curl -X POST "$BASE_URL/api/v1/rides/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "Test Coaster", "category": "RC", "park": 1}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Ride" + curl -X PUT "$BASE_URL/api/v1/rides/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "Updated Ride Name"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Ride" + curl -X DELETE "$BASE_URL/api/v1/rides/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Create Ride Photo" + curl -X POST "$BASE_URL/api/v1/rides/1/photos/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -F "image=@/path/to/photo.jpg" \ + -F "caption=Test ride photo" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Ride Photo" + curl -X PUT "$BASE_URL/api/v1/rides/1/photos/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"caption": "Updated ride photo caption"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Ride Photo" + curl -X DELETE "$BASE_URL/api/v1/rides/1/photos/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# ACCOUNTS API ENDPOINTS (/api/v1/accounts/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== ACCOUNTS API ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. List User Profiles" + curl -X GET "$BASE_URL/api/v1/accounts/profiles/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. User Profile Detail" + curl -X GET "$BASE_URL/api/v1/accounts/profiles/1/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. List Top Lists" + curl -X GET "$BASE_URL/api/v1/accounts/toplists/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Top List Detail" + curl -X GET "$BASE_URL/api/v1/accounts/toplists/1/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. List Top List Items" + curl -X GET "$BASE_URL/api/v1/accounts/toplist-items/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Top List Item Detail" + curl -X GET "$BASE_URL/api/v1/accounts/toplist-items/1/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Update User Profile" + curl -X PUT "$BASE_URL/api/v1/accounts/profiles/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"bio": "Updated bio"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Create Top List" + curl -X POST "$BASE_URL/api/v1/accounts/toplists/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "My Top Coasters", "description": "My favorite roller coasters"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Top List" + curl -X PUT "$BASE_URL/api/v1/accounts/toplists/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"name": "Updated Top List Name"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Top List" + curl -X DELETE "$BASE_URL/api/v1/accounts/toplists/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Create Top List Item" + curl -X POST "$BASE_URL/api/v1/accounts/toplist-items/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"toplist": 1, "ride": 1, "position": 1}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Update Top List Item" + curl -X PUT "$BASE_URL/api/v1/accounts/toplist-items/1/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"position": 2}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Delete Top List Item" + curl -X DELETE "$BASE_URL/api/v1/accounts/toplist-items/1/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# HISTORY API ENDPOINTS (/api/v1/history/) +# ============================================================================ +if should_run_endpoint false false; then + echo -e "\n\n=== HISTORY API ENDPOINTS ===" + + echo "$ENDPOINT_NUM. Park History List" + curl -X GET "$BASE_URL/api/v1/history/parks/park-slug/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Park History Detail" + curl -X GET "$BASE_URL/api/v1/history/parks/park-slug/detail/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride History List" + curl -X GET "$BASE_URL/api/v1/history/parks/park-slug/rides/ride-slug/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Ride History Detail" + curl -X GET "$BASE_URL/api/v1/history/parks/park-slug/rides/ride-slug/detail/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Unified Timeline" + curl -X GET "$BASE_URL/api/v1/history/timeline/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Unified Timeline Detail" + curl -X GET "$BASE_URL/api/v1/history/timeline/1/" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# EMAIL API ENDPOINTS (/api/v1/email/) +# ============================================================================ +if should_run_endpoint true false; then + echo -e "\n\n=== EMAIL API ENDPOINTS ===" + + echo "$ENDPOINT_NUM. Send Email" + curl -X POST "$BASE_URL/api/v1/email/send/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" \ + -d '{"to": "recipient@example.com", "subject": "Test", "message": "Test message"}' + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# CORE API ENDPOINTS (/api/v1/core/) +# ============================================================================ +if should_run_endpoint false false; then + echo -e "\n\n=== CORE API ENDPOINTS ===" + + echo "$ENDPOINT_NUM. Entity Fuzzy Search" + curl -X GET "$BASE_URL/api/v1/core/entities/search/?q=disney" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Entity Not Found" + curl -X POST "$BASE_URL/api/v1/core/entities/not-found/" \ + -H "Content-Type: application/json" \ + -d '{"query": "nonexistent park", "type": "park"}' + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Entity Suggestions" + curl -X GET "$BASE_URL/api/v1/core/entities/suggestions/?q=magic" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# MAPS API ENDPOINTS (/api/v1/maps/) +# ============================================================================ +if should_run_endpoint false false || should_run_endpoint true false; then + echo -e "\n\n=== MAPS API ENDPOINTS ===" +fi + +if should_run_endpoint false false; then + echo "$ENDPOINT_NUM. Map Locations" + curl -X GET "$BASE_URL/api/v1/maps/locations/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Map Location Detail" + curl -X GET "$BASE_URL/api/v1/maps/locations/park/1/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Map Search" + curl -X GET "$BASE_URL/api/v1/maps/search/?q=disney" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Map Bounds Query" + curl -X GET "$BASE_URL/api/v1/maps/bounds/?north=40.7&south=40.6&east=-73.9&west=-74.0" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Map Statistics" + curl -X GET "$BASE_URL/api/v1/maps/stats/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Map Cache Status" + curl -X GET "$BASE_URL/api/v1/maps/cache/" + ((ENDPOINT_NUM++)) +fi + +if should_run_endpoint true false; then + echo -e "\n$ENDPOINT_NUM. Invalidate Map Cache" + curl -X POST "$BASE_URL/api/v1/maps/cache/invalidate/" \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# API DOCUMENTATION ENDPOINTS +# ============================================================================ +if should_run_endpoint false true; then + echo -e "\n\n=== API DOCUMENTATION ENDPOINTS ===" + + echo "$ENDPOINT_NUM. OpenAPI Schema" + curl -X GET "$BASE_URL/api/schema/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. Swagger UI" + curl -X GET "$BASE_URL/api/docs/" + ((ENDPOINT_NUM++)) + + echo -e "\n$ENDPOINT_NUM. ReDoc" + curl -X GET "$BASE_URL/api/redoc/" + ((ENDPOINT_NUM++)) +fi + +# ============================================================================ +# HEALTH CHECK (Django Health Check) +# ============================================================================ +if should_run_endpoint false false; then + echo -e "\n\n=== DJANGO HEALTH CHECK ===" + + echo "$ENDPOINT_NUM. Django Health Check" + curl -X GET "$BASE_URL/health/" + ((ENDPOINT_NUM++)) +fi + +echo -e "\n\n=== END OF API ENDPOINTS TEST SUITE ===" +echo "Total endpoints tested: $((ENDPOINT_NUM - 1))" +echo "" +echo "Notes:" +echo "- Replace YOUR_TOKEN_HERE with actual authentication tokens" +echo "- Replace /path/to/photo.jpg with actual file paths for photo uploads" +echo "- Replace numeric IDs (1, 2, etc.) with actual resource IDs" +echo "- Replace slug placeholders (park-slug, ride-slug) with actual slugs" +echo "- Adjust BASE_URL for your environment (localhost:8000, staging, production)" +echo "" +echo "Authentication required endpoints are marked with Authorization header" +echo "File upload endpoints use multipart/form-data (-F flag)" +echo "JSON endpoints use application/json content type" diff --git a/backend/apps/api/apps.py b/backend/apps/api/apps.py index 104c10e5..de0f41f6 100644 --- a/backend/apps/api/apps.py +++ b/backend/apps/api/apps.py @@ -17,3 +17,7 @@ class ApiConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "api" verbose_name = "ThrillWiki API" + + def ready(self): + """Import signals when the app is ready.""" + import apps.api.v1.signals # noqa: F401 diff --git a/backend/apps/api/v1/maps/views.py b/backend/apps/api/v1/maps/views.py index 5e316e39..cd9a0eca 100644 --- a/backend/apps/api/v1/maps/views.py +++ b/backend/apps/api/v1/maps/views.py @@ -4,8 +4,14 @@ Migrated from apps.core.views.map_views """ import logging +from typing import Dict, List, Any, Optional from django.http import HttpRequest +from django.db.models import Q +from django.core.cache import cache +from django.contrib.gis.geos import Polygon +from django.contrib.gis.db.models.functions import Distance +from django.contrib.gis.geos import Point from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status @@ -13,6 +19,16 @@ from rest_framework.permissions import AllowAny from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter from drf_spectacular.types import OpenApiTypes +from apps.parks.models import Park, ParkLocation +from apps.rides.models import Ride +from ..serializers.maps import ( + MapLocationSerializer, + MapLocationsResponseSerializer, + MapSearchResultSerializer, + MapSearchResponseSerializer, + MapLocationDetailSerializer, +) + logger = logging.getLogger(__name__) @@ -26,59 +42,71 @@ logger = logging.getLogger(__name__) type=OpenApiTypes.NUMBER, location=OpenApiParameter.QUERY, required=False, - description="Northern latitude bound", + description="Northern latitude bound (-90 to 90). Used with south, east, west to define geographic bounds.", + examples=[41.5], ), OpenApiParameter( "south", type=OpenApiTypes.NUMBER, location=OpenApiParameter.QUERY, required=False, - description="Southern latitude bound", + description="Southern latitude bound (-90 to 90). Must be less than north bound.", + examples=[41.4], ), OpenApiParameter( "east", type=OpenApiTypes.NUMBER, location=OpenApiParameter.QUERY, required=False, - description="Eastern longitude bound", + description="Eastern longitude bound (-180 to 180). Must be greater than west bound.", + examples=[-82.6], ), OpenApiParameter( "west", type=OpenApiTypes.NUMBER, location=OpenApiParameter.QUERY, required=False, - description="Western longitude bound", + description="Western longitude bound (-180 to 180). Used with other bounds for geographic filtering.", + examples=[-82.8], ), OpenApiParameter( "zoom", type=OpenApiTypes.INT, location=OpenApiParameter.QUERY, required=False, - description="Map zoom level", + description="Map zoom level (1-20). Higher values show more detail. Used for clustering decisions.", + examples=[10], ), OpenApiParameter( "types", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, - description="Comma-separated location types", + description="Comma-separated location types to include. Valid values: 'park', 'ride'. Default: 'park,ride'", + examples=["park,ride", "park", "ride"], ), OpenApiParameter( "cluster", type=OpenApiTypes.BOOL, location=OpenApiParameter.QUERY, required=False, - description="Enable clustering", + description="Enable location clustering for high-density areas. Default: false", + examples=[True, False], ), OpenApiParameter( "q", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, required=False, - description="Text query", + description="Text search query. Searches park/ride names, cities, and states.", + examples=["Cedar Point", "roller coaster", "Ohio"], ), ], - responses={200: OpenApiTypes.OBJECT}, + responses={ + 200: MapLocationsResponseSerializer, + 400: OpenApiTypes.OBJECT, + 500: OpenApiTypes.OBJECT, + }, tags=["Maps"], ), ) @@ -90,15 +118,151 @@ class MapLocationsAPIView(APIView): def get(self, request: HttpRequest) -> Response: """Get map locations with optional clustering and filtering.""" try: - # Simple implementation to fix import error - # TODO: Implement full functionality - return Response( - { - "status": "success", - "message": "Map locations endpoint - implementation needed", - "data": [], - } - ) + # Parse query parameters + north = request.GET.get("north") + south = request.GET.get("south") + east = request.GET.get("east") + west = request.GET.get("west") + zoom = request.GET.get("zoom", 10) + types = request.GET.get("types", "park,ride").split(",") + cluster = request.GET.get("cluster", "false").lower() == "true" + query = request.GET.get("q", "").strip() + + # Build cache key + cache_key = f"map_locations_{north}_{south}_{east}_{west}_{zoom}_{','.join(types)}_{cluster}_{query}" + cached_result = cache.get(cache_key) + if cached_result: + return Response(cached_result) + + locations = [] + total_count = 0 + + # Get parks if requested + if "park" in types: + parks_query = Park.objects.select_related("location", "operator").filter( + location__point__isnull=False + ) + + # Apply bounds filtering + if all([north, south, east, west]): + try: + bounds_polygon = Polygon.from_bbox(( + float(west), float(south), float(east), float(north) + )) + parks_query = parks_query.filter( + location__point__within=bounds_polygon) + except (ValueError, TypeError): + pass + + # Apply text search + if query: + parks_query = parks_query.filter( + Q(name__icontains=query) | + Q(location__city__icontains=query) | + Q(location__state__icontains=query) + ) + + # Serialize parks + for park in parks_query[:100]: # Limit results + park_data = { + "id": park.id, + "type": "park", + "name": park.name, + "slug": park.slug, + "latitude": park.location.latitude if hasattr(park, 'location') and park.location else None, + "longitude": park.location.longitude if hasattr(park, 'location') and park.location else None, + "status": park.status, + "location": { + "city": park.location.city if hasattr(park, 'location') and park.location else "", + "state": park.location.state if hasattr(park, 'location') and park.location else "", + "country": park.location.country if hasattr(park, 'location') and park.location else "", + "formatted_address": park.location.formatted_address if hasattr(park, 'location') and park.location else "", + }, + "stats": { + "coaster_count": park.coaster_count or 0, + "ride_count": park.ride_count or 0, + "average_rating": float(park.average_rating) if park.average_rating else None, + }, + } + locations.append(park_data) + + # Get rides if requested + if "ride" in types: + rides_query = Ride.objects.select_related("park__location", "manufacturer").filter( + park__location__point__isnull=False + ) + + # Apply bounds filtering + if all([north, south, east, west]): + try: + bounds_polygon = Polygon.from_bbox(( + float(west), float(south), float(east), float(north) + )) + rides_query = rides_query.filter( + park__location__point__within=bounds_polygon) + except (ValueError, TypeError): + pass + + # Apply text search + if query: + rides_query = rides_query.filter( + Q(name__icontains=query) | + Q(park__name__icontains=query) | + Q(park__location__city__icontains=query) + ) + + # Serialize rides + for ride in rides_query[:100]: # Limit results + ride_data = { + "id": ride.id, + "type": "ride", + "name": ride.name, + "slug": ride.slug, + "latitude": ride.park.location.latitude if hasattr(ride.park, 'location') and ride.park.location else None, + "longitude": ride.park.location.longitude if hasattr(ride.park, 'location') and ride.park.location else None, + "status": ride.status, + "location": { + "city": ride.park.location.city if hasattr(ride.park, 'location') and ride.park.location else "", + "state": ride.park.location.state if hasattr(ride.park, 'location') and ride.park.location else "", + "country": ride.park.location.country if hasattr(ride.park, 'location') and ride.park.location else "", + "formatted_address": ride.park.location.formatted_address if hasattr(ride.park, 'location') and ride.park.location else "", + }, + "stats": { + "category": ride.get_category_display() if ride.category else None, + "average_rating": float(ride.average_rating) if ride.average_rating else None, + "park_name": ride.park.name, + }, + } + locations.append(ride_data) + + total_count = len(locations) + + # Calculate bounds from results + bounds = {} + if locations: + lats = [loc["latitude"] for loc in locations if loc["latitude"]] + lngs = [loc["longitude"] for loc in locations if loc["longitude"]] + if lats and lngs: + bounds = { + "north": max(lats), + "south": min(lats), + "east": max(lngs), + "west": min(lngs), + } + + result = { + "status": "success", + "locations": locations, + "clusters": [], # TODO: Implement clustering + "bounds": bounds, + "total_count": total_count, + "clustered": cluster, + } + + # Cache result for 5 minutes + cache.set(cache_key, result, 300) + + return Response(result) except Exception as e: logger.error(f"Error in MapLocationsAPIView: {str(e)}", exc_info=True) @@ -128,7 +292,12 @@ class MapLocationsAPIView(APIView): description="ID of the location", ), ], - responses={200: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT}, + responses={ + 200: MapLocationDetailSerializer, + 400: OpenApiTypes.OBJECT, + 404: OpenApiTypes.OBJECT, + 500: OpenApiTypes.OBJECT, + }, tags=["Maps"], ), ) @@ -142,17 +311,90 @@ class MapLocationDetailAPIView(APIView): ) -> Response: """Get detailed information for a specific location.""" try: - # Simple implementation to fix import error - return Response( - { - "status": "success", - "message": f"Location detail for {location_type}/{location_id} - implementation needed", - "data": { - "location_type": location_type, - "location_id": location_id, + if location_type == "park": + try: + obj = Park.objects.select_related( + "location", "operator").get(id=location_id) + except Park.DoesNotExist: + return Response( + {"status": "error", "message": "Park not found"}, + status=status.HTTP_404_NOT_FOUND, + ) + elif location_type == "ride": + try: + obj = Ride.objects.select_related( + "park__location", "manufacturer").get(id=location_id) + except Ride.DoesNotExist: + return Response( + {"status": "error", "message": "Ride not found"}, + status=status.HTTP_404_NOT_FOUND, + ) + else: + return Response( + {"status": "error", "message": "Invalid location type"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Serialize the object + if location_type == "park": + data = { + "id": obj.id, + "type": "park", + "name": obj.name, + "slug": obj.slug, + "description": obj.description, + "latitude": obj.location.latitude if hasattr(obj, 'location') and obj.location else None, + "longitude": obj.location.longitude if hasattr(obj, 'location') and obj.location else None, + "status": obj.status, + "location": { + "street_address": obj.location.street_address if hasattr(obj, 'location') and obj.location else "", + "city": obj.location.city if hasattr(obj, 'location') and obj.location else "", + "state": obj.location.state if hasattr(obj, 'location') and obj.location else "", + "country": obj.location.country if hasattr(obj, 'location') and obj.location else "", + "postal_code": obj.location.postal_code if hasattr(obj, 'location') and obj.location else "", + "formatted_address": obj.location.formatted_address if hasattr(obj, 'location') and obj.location else "", }, + "stats": { + "coaster_count": obj.coaster_count or 0, + "ride_count": obj.ride_count or 0, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + "size_acres": float(obj.size_acres) if obj.size_acres else None, + "opening_date": obj.opening_date.isoformat() if obj.opening_date else None, + }, + "nearby_locations": [], # TODO: Implement nearby locations } - ) + else: # ride + data = { + "id": obj.id, + "type": "ride", + "name": obj.name, + "slug": obj.slug, + "description": obj.description, + "latitude": obj.park.location.latitude if hasattr(obj.park, 'location') and obj.park.location else None, + "longitude": obj.park.location.longitude if hasattr(obj.park, 'location') and obj.park.location else None, + "status": obj.status, + "location": { + "street_address": obj.park.location.street_address if hasattr(obj.park, 'location') and obj.park.location else "", + "city": obj.park.location.city if hasattr(obj.park, 'location') and obj.park.location else "", + "state": obj.park.location.state if hasattr(obj.park, 'location') and obj.park.location else "", + "country": obj.park.location.country if hasattr(obj.park, 'location') and obj.park.location else "", + "postal_code": obj.park.location.postal_code if hasattr(obj.park, 'location') and obj.park.location else "", + "formatted_address": obj.park.location.formatted_address if hasattr(obj.park, 'location') and obj.park.location else "", + }, + "stats": { + "category": obj.get_category_display() if obj.category else None, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + "park_name": obj.park.name, + "opening_date": obj.opening_date.isoformat() if obj.opening_date else None, + "manufacturer": obj.manufacturer.name if obj.manufacturer else None, + }, + "nearby_locations": [], # TODO: Implement nearby locations + } + + return Response({ + "status": "success", + "data": data, + }) except Exception as e: logger.error(f"Error in MapLocationDetailAPIView: {str(e)}", exc_info=True) @@ -174,8 +416,33 @@ class MapLocationDetailAPIView(APIView): required=True, description="Search query", ), + OpenApiParameter( + "types", + type=OpenApiTypes.STR, + location=OpenApiParameter.QUERY, + required=False, + description="Comma-separated location types (park,ride)", + ), + OpenApiParameter( + "page", + type=OpenApiTypes.INT, + location=OpenApiParameter.QUERY, + required=False, + description="Page number", + ), + OpenApiParameter( + "page_size", + type=OpenApiTypes.INT, + location=OpenApiParameter.QUERY, + required=False, + description="Results per page", + ), ], - responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT}, + responses={ + 200: MapSearchResponseSerializer, + 400: OpenApiTypes.OBJECT, + 500: OpenApiTypes.OBJECT, + }, tags=["Maps"], ), ) @@ -197,14 +464,76 @@ class MapSearchAPIView(APIView): status=status.HTTP_400_BAD_REQUEST, ) - # Simple implementation to fix import error - return Response( - { - "status": "success", - "message": f"Search for '{query}' - implementation needed", - "data": [], - } - ) + types = request.GET.get("types", "park,ride").split(",") + page = int(request.GET.get("page", 1)) + page_size = min(int(request.GET.get("page_size", 20)), 100) + + results = [] + total_count = 0 + + # Search parks + if "park" in types: + parks_query = Park.objects.select_related("location").filter( + Q(name__icontains=query) | + Q(location__city__icontains=query) | + Q(location__state__icontains=query) + ).filter(location__point__isnull=False) + + for park in parks_query[:50]: # Limit results + results.append({ + "id": park.id, + "type": "park", + "name": park.name, + "slug": park.slug, + "latitude": park.location.latitude if hasattr(park, 'location') and park.location else None, + "longitude": park.location.longitude if hasattr(park, 'location') and park.location else None, + "location": { + "city": park.location.city if hasattr(park, 'location') and park.location else "", + "state": park.location.state if hasattr(park, 'location') and park.location else "", + "country": park.location.country if hasattr(park, 'location') and park.location else "", + }, + "relevance_score": 1.0, # TODO: Implement relevance scoring + }) + + # Search rides + if "ride" in types: + rides_query = Ride.objects.select_related("park__location").filter( + Q(name__icontains=query) | + Q(park__name__icontains=query) | + Q(park__location__city__icontains=query) + ).filter(park__location__point__isnull=False) + + for ride in rides_query[:50]: # Limit results + results.append({ + "id": ride.id, + "type": "ride", + "name": ride.name, + "slug": ride.slug, + "latitude": ride.park.location.latitude if hasattr(ride.park, 'location') and ride.park.location else None, + "longitude": ride.park.location.longitude if hasattr(ride.park, 'location') and ride.park.location else None, + "location": { + "city": ride.park.location.city if hasattr(ride.park, 'location') and ride.park.location else "", + "state": ride.park.location.state if hasattr(ride.park, 'location') and ride.park.location else "", + "country": ride.park.location.country if hasattr(ride.park, 'location') and ride.park.location else "", + }, + "relevance_score": 1.0, # TODO: Implement relevance scoring + }) + + total_count = len(results) + + # Apply pagination + start_idx = (page - 1) * page_size + end_idx = start_idx + page_size + paginated_results = results[start_idx:end_idx] + + return Response({ + "status": "success", + "results": paginated_results, + "query": query, + "total_count": total_count, + "page": page, + "page_size": page_size, + }) except Exception as e: logger.error(f"Error in MapSearchAPIView: {str(e)}", exc_info=True) @@ -247,6 +576,13 @@ class MapSearchAPIView(APIView): required=True, description="Western longitude bound", ), + OpenApiParameter( + "types", + type=OpenApiTypes.STR, + location=OpenApiParameter.QUERY, + required=False, + description="Comma-separated location types (park,ride)", + ), ], responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT}, tags=["Maps"], @@ -260,22 +596,87 @@ class MapBoundsAPIView(APIView): def get(self, request: HttpRequest) -> Response: """Get locations within specific geographic bounds.""" try: - # Simple implementation to fix import error - return Response( - { - "status": "success", - "message": "Bounds query - implementation needed", - "data": [], - } - ) + # Parse required bounds parameters + try: + north = float(request.GET.get("north")) + south = float(request.GET.get("south")) + east = float(request.GET.get("east")) + west = float(request.GET.get("west")) + except (TypeError, ValueError): + return Response( + {"status": "error", "message": "Invalid bounds parameters"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Validate bounds + if north <= south: + return Response( + {"status": "error", "message": "North bound must be greater than south bound"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if west >= east: + return Response( + {"status": "error", "message": "West bound must be less than east bound"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + types = request.GET.get("types", "park,ride").split(",") + locations = [] + + # Create bounds polygon + bounds_polygon = Polygon.from_bbox((west, south, east, north)) + + # Get parks within bounds + if "park" in types: + parks_query = Park.objects.select_related("location").filter( + location__point__within=bounds_polygon + ) + + for park in parks_query[:100]: # Limit results + locations.append({ + "id": park.id, + "type": "park", + "name": park.name, + "slug": park.slug, + "latitude": park.location.latitude if hasattr(park, 'location') and park.location else None, + "longitude": park.location.longitude if hasattr(park, 'location') and park.location else None, + "status": park.status, + }) + + # Get rides within bounds + if "ride" in types: + rides_query = Ride.objects.select_related("park__location").filter( + park__location__point__within=bounds_polygon + ) + + for ride in rides_query[:100]: # Limit results + locations.append({ + "id": ride.id, + "type": "ride", + "name": ride.name, + "slug": ride.slug, + "latitude": ride.park.location.latitude if hasattr(ride.park, 'location') and ride.park.location else None, + "longitude": ride.park.location.longitude if hasattr(ride.park, 'location') and ride.park.location else None, + "status": ride.status, + }) + + return Response({ + "status": "success", + "locations": locations, + "bounds": { + "north": north, + "south": south, + "east": east, + "west": west, + }, + "total_count": len(locations), + }) except Exception as e: logger.error(f"Error in MapBoundsAPIView: {str(e)}", exc_info=True) return Response( - { - "status": "error", - "message": "Failed to retrieve locations within bounds", - }, + {"status": "error", "message": "Failed to retrieve locations within bounds"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @@ -296,15 +697,26 @@ class MapStatsAPIView(APIView): def get(self, request: HttpRequest) -> Response: """Get map service statistics and performance metrics.""" try: - # Simple implementation to fix import error - return Response( - { - "status": "success", - "data": {"total_locations": 0, "cache_hits": 0, "cache_misses": 0}, - } - ) + # Count locations with coordinates + parks_with_location = Park.objects.filter( + location__point__isnull=False).count() + rides_with_location = Ride.objects.filter( + park__location__point__isnull=False).count() + total_locations = parks_with_location + rides_with_location + + return Response({ + "status": "success", + "data": { + "total_locations": total_locations, + "parks_with_location": parks_with_location, + "rides_with_location": rides_with_location, + "cache_hits": 0, # TODO: Implement cache statistics + "cache_misses": 0, # TODO: Implement cache statistics + }, + }) except Exception as e: + logger.error(f"Error in MapStatsAPIView: {str(e)}", exc_info=True) return Response( {"error": f"Internal server error: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -333,12 +745,21 @@ class MapCacheAPIView(APIView): def delete(self, request: HttpRequest) -> Response: """Clear all map cache (admin only).""" try: - # Simple implementation to fix import error - return Response( - {"status": "success", "message": "Map cache cleared successfully"} - ) + # Clear all map-related cache keys + cache_keys = cache.keys("map_*") + if cache_keys: + cache.delete_many(cache_keys) + cleared_count = len(cache_keys) + else: + cleared_count = 0 + + return Response({ + "status": "success", + "message": f"Map cache cleared successfully. Cleared {cleared_count} entries.", + }) except Exception as e: + logger.error(f"Error in MapCacheAPIView.delete: {str(e)}", exc_info=True) return Response( {"error": f"Internal server error: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -347,12 +768,21 @@ class MapCacheAPIView(APIView): def post(self, request: HttpRequest) -> Response: """Invalidate specific cache entries.""" try: - # Simple implementation to fix import error - return Response( - {"status": "success", "message": "Cache invalidated successfully"} - ) + # Get cache keys to invalidate from request data + cache_keys = request.data.get("cache_keys", []) + if cache_keys: + cache.delete_many(cache_keys) + invalidated_count = len(cache_keys) + else: + invalidated_count = 0 + + return Response({ + "status": "success", + "message": f"Cache invalidated successfully. Invalidated {invalidated_count} entries.", + }) except Exception as e: + logger.error(f"Error in MapCacheAPIView.post: {str(e)}", exc_info=True) return Response( {"error": f"Internal server error: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/apps/api/v1/serializers/__init__.py b/backend/apps/api/v1/serializers/__init__.py index 82d7981c..d436956d 100644 --- a/backend/apps/api/v1/serializers/__init__.py +++ b/backend/apps/api/v1/serializers/__init__.py @@ -146,9 +146,10 @@ def _import_accounts_symbols() -> Dict[str, Any]: _accounts = _import_accounts_symbols() -# Bind account symbols into the module namespace (either actual objects or None) +# Bind account symbols into the module namespace (only if they exist) for _name in _ACCOUNTS_SYMBOLS: - globals()[_name] = _accounts.get(_name) + if _accounts.get(_name) is not None: + globals()[_name] = _accounts[_name] # --- Services domain --- @@ -255,22 +256,79 @@ _SERVICES_EXPORTS = [ "DistanceCalculationOutputSerializer", ] -# Build __all__ from known exports plus any serializer-like names discovered above -__all__ = ( - _SHARED_EXPORTS - + _PARKS_EXPORTS - + _COMPANIES_EXPORTS - + _RIDES_EXPORTS - + _SERVICES_EXPORTS - + _ACCOUNTS_SYMBOLS -) +# Build a static __all__ list with only the serializers we know exist +__all__ = [ + # Shared exports + "CATEGORY_CHOICES", + "ModelChoices", + "LocationOutputSerializer", + "CompanyOutputSerializer", + "UserModel", + + # Parks exports + "ParkListOutputSerializer", + "ParkDetailOutputSerializer", + "ParkCreateInputSerializer", + "ParkUpdateInputSerializer", + "ParkFilterInputSerializer", + "ParkAreaDetailOutputSerializer", + "ParkAreaCreateInputSerializer", + "ParkAreaUpdateInputSerializer", + "ParkLocationOutputSerializer", + "ParkLocationCreateInputSerializer", + "ParkLocationUpdateInputSerializer", + "ParkSuggestionSerializer", + "ParkSuggestionOutputSerializer", + + # Companies exports + "CompanyDetailOutputSerializer", + "CompanyCreateInputSerializer", + "CompanyUpdateInputSerializer", + "RideModelDetailOutputSerializer", + "RideModelCreateInputSerializer", + "RideModelUpdateInputSerializer", + + # Rides exports + "RideParkOutputSerializer", + "RideModelOutputSerializer", + "RideListOutputSerializer", + "RideDetailOutputSerializer", + "RideCreateInputSerializer", + "RideUpdateInputSerializer", + "RideFilterInputSerializer", + "RollerCoasterStatsOutputSerializer", + "RollerCoasterStatsCreateInputSerializer", + "RollerCoasterStatsUpdateInputSerializer", + "RideLocationOutputSerializer", + "RideLocationCreateInputSerializer", + "RideLocationUpdateInputSerializer", + "RideReviewOutputSerializer", + "RideReviewCreateInputSerializer", + "RideReviewUpdateInputSerializer", + + # Services exports + "HealthCheckOutputSerializer", + "PerformanceMetricsOutputSerializer", + "SimpleHealthOutputSerializer", + "EmailSendInputSerializer", + "EmailTemplateOutputSerializer", + "MapDataOutputSerializer", + "CoordinateInputSerializer", + "HistoryEventSerializer", + "HistoryEntryOutputSerializer", + "HistoryCreateInputSerializer", + "ModerationSubmissionSerializer", + "ModerationSubmissionOutputSerializer", + "RoadtripParkSerializer", + "RoadtripCreateInputSerializer", + "RoadtripOutputSerializer", + "GeocodeInputSerializer", + "GeocodeOutputSerializer", + "DistanceCalculationInputSerializer", + "DistanceCalculationOutputSerializer", +] -# Add any discovered globals that look like serializers (avoid duplicates) -for name in list(globals().keys()): - if name in __all__: - continue - if name.endswith(("Serializer", "OutputSerializer", "InputSerializer")): +# Add any accounts serializers that actually exist +for name in _ACCOUNTS_SYMBOLS: + if name in globals(): __all__.append(name) - -# Ensure __all__ is a flat list of unique strings (preserve order) -__all__ = list(dict.fromkeys(__all__)) diff --git a/backend/apps/api/v1/serializers/maps.py b/backend/apps/api/v1/serializers/maps.py new file mode 100644 index 00000000..c8ff7139 --- /dev/null +++ b/backend/apps/api/v1/serializers/maps.py @@ -0,0 +1,408 @@ +""" +Maps domain serializers for ThrillWiki API v1. + +This module contains all serializers related to map functionality, +including location data, search results, and clustering. +""" + +from rest_framework import serializers +from drf_spectacular.utils import ( + extend_schema_serializer, + extend_schema_field, + OpenApiExample, +) + + +# === MAP LOCATION SERIALIZERS === + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Location Example", + summary="Example map location response", + description="A location point on the map", + value={ + "id": 1, + "type": "park", + "name": "Cedar Point", + "slug": "cedar-point", + "latitude": 41.4793, + "longitude": -82.6833, + "status": "OPERATING", + "location": { + "city": "Sandusky", + "state": "Ohio", + "country": "United States", + }, + "stats": { + "coaster_count": 17, + "ride_count": 70, + "average_rating": 4.5, + }, + }, + ) + ] +) +class MapLocationSerializer(serializers.Serializer): + """Serializer for individual map locations (parks and rides).""" + + id = serializers.IntegerField() + type = serializers.CharField() # 'park' or 'ride' + name = serializers.CharField() + slug = serializers.CharField() + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + status = serializers.CharField() + + # Location details + location = serializers.SerializerMethodField() + + # Statistics + stats = serializers.SerializerMethodField() + + @extend_schema_field(serializers.DictField()) + def get_location(self, obj) -> dict: + """Get location information.""" + if hasattr(obj, 'location') and obj.location: + return { + "city": obj.location.city, + "state": obj.location.state, + "country": obj.location.country, + "formatted_address": obj.location.formatted_address, + } + return {} + + @extend_schema_field(serializers.DictField()) + def get_stats(self, obj) -> dict: + """Get relevant statistics based on object type.""" + if obj._meta.model_name == 'park': + return { + "coaster_count": obj.coaster_count or 0, + "ride_count": obj.ride_count or 0, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + } + elif obj._meta.model_name == 'ride': + return { + "category": obj.get_category_display() if obj.category else None, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + "park_name": obj.park.name if obj.park else None, + } + return {} + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Cluster Example", + summary="Example map cluster response", + description="A cluster of locations on the map", + value={ + "id": "cluster_1", + "type": "cluster", + "latitude": 41.5, + "longitude": -82.7, + "count": 5, + "bounds": { + "north": 41.6, + "south": 41.4, + "east": -82.6, + "west": -82.8, + }, + }, + ) + ] +) +class MapClusterSerializer(serializers.Serializer): + """Serializer for map clusters.""" + + id = serializers.CharField() + type = serializers.CharField(default="cluster") + latitude = serializers.FloatField() + longitude = serializers.FloatField() + count = serializers.IntegerField() + bounds = serializers.DictField() + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Locations Response Example", + summary="Example map locations response", + description="Response containing locations and optional clusters", + value={ + "status": "success", + "data": { + "locations": [ + { + "id": 1, + "type": "park", + "name": "Cedar Point", + "slug": "cedar-point", + "latitude": 41.4793, + "longitude": -82.6833, + "status": "OPERATING", + } + ], + "clusters": [], + "bounds": { + "north": 41.5, + "south": 41.4, + "east": -82.6, + "west": -82.8, + }, + "total_count": 1, + "clustered": False, + }, + }, + ) + ] +) +class MapLocationsResponseSerializer(serializers.Serializer): + """Response serializer for map locations endpoint.""" + + status = serializers.CharField(default="success") + locations = serializers.ListField(child=serializers.DictField()) + clusters = serializers.ListField(child=serializers.DictField(), default=list) + bounds = serializers.DictField(default=dict) + total_count = serializers.IntegerField(default=0) + clustered = serializers.BooleanField(default=False) + + +# === MAP SEARCH SERIALIZERS === + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Search Result Example", + summary="Example map search result", + description="A search result for map locations", + value={ + "id": 1, + "type": "park", + "name": "Cedar Point", + "slug": "cedar-point", + "latitude": 41.4793, + "longitude": -82.6833, + "location": { + "city": "Sandusky", + "state": "Ohio", + "country": "United States", + }, + "relevance_score": 0.95, + }, + ) + ] +) +class MapSearchResultSerializer(serializers.Serializer): + """Serializer for map search results.""" + + id = serializers.IntegerField() + type = serializers.CharField() + name = serializers.CharField() + slug = serializers.CharField() + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + location = serializers.SerializerMethodField() + relevance_score = serializers.FloatField(required=False) + + @extend_schema_field(serializers.DictField()) + def get_location(self, obj) -> dict: + """Get location information.""" + if hasattr(obj, 'location') and obj.location: + return { + "city": obj.location.city, + "state": obj.location.state, + "country": obj.location.country, + } + return {} + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Search Response Example", + summary="Example map search response", + description="Response containing search results", + value={ + "status": "success", + "data": { + "results": [ + { + "id": 1, + "type": "park", + "name": "Cedar Point", + "slug": "cedar-point", + "latitude": 41.4793, + "longitude": -82.6833, + } + ], + "query": "cedar point", + "total_count": 1, + "page": 1, + "page_size": 20, + }, + }, + ) + ] +) +class MapSearchResponseSerializer(serializers.Serializer): + """Response serializer for map search endpoint.""" + + status = serializers.CharField(default="success") + results = serializers.ListField(child=serializers.DictField()) + query = serializers.CharField() + total_count = serializers.IntegerField(default=0) + page = serializers.IntegerField(default=1) + page_size = serializers.IntegerField(default=20) + + +# === MAP DETAIL SERIALIZERS === + + +@extend_schema_serializer( + examples=[ + OpenApiExample( + "Map Location Detail Example", + summary="Example map location detail response", + description="Detailed information about a specific location", + value={ + "id": 1, + "type": "park", + "name": "Cedar Point", + "slug": "cedar-point", + "description": "America's Roller Coast", + "latitude": 41.4793, + "longitude": -82.6833, + "status": "OPERATING", + "location": { + "street_address": "1 Cedar Point Dr", + "city": "Sandusky", + "state": "Ohio", + "country": "United States", + "postal_code": "44870", + "formatted_address": "1 Cedar Point Dr, Sandusky, Ohio, 44870, United States", + }, + "stats": { + "coaster_count": 17, + "ride_count": 70, + "average_rating": 4.5, + }, + "nearby_locations": [], + }, + ) + ] +) +class MapLocationDetailSerializer(serializers.Serializer): + """Serializer for detailed map location information.""" + + id = serializers.IntegerField() + type = serializers.CharField() + name = serializers.CharField() + slug = serializers.CharField() + description = serializers.CharField() + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + status = serializers.CharField() + + # Detailed location information + location = serializers.SerializerMethodField() + + # Statistics + stats = serializers.SerializerMethodField() + + # Nearby locations + nearby_locations = serializers.SerializerMethodField() + + @extend_schema_field(serializers.DictField()) + def get_location(self, obj) -> dict: + """Get detailed location information.""" + if hasattr(obj, 'location') and obj.location: + return { + "street_address": obj.location.street_address, + "city": obj.location.city, + "state": obj.location.state, + "country": obj.location.country, + "postal_code": obj.location.postal_code, + "formatted_address": obj.location.formatted_address, + } + return {} + + @extend_schema_field(serializers.DictField()) + def get_stats(self, obj) -> dict: + """Get detailed statistics based on object type.""" + if obj._meta.model_name == 'park': + return { + "coaster_count": obj.coaster_count or 0, + "ride_count": obj.ride_count or 0, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + "size_acres": float(obj.size_acres) if obj.size_acres else None, + "opening_date": obj.opening_date.isoformat() if obj.opening_date else None, + } + elif obj._meta.model_name == 'ride': + return { + "category": obj.get_category_display() if obj.category else None, + "average_rating": float(obj.average_rating) if obj.average_rating else None, + "park_name": obj.park.name if obj.park else None, + "opening_date": obj.opening_date.isoformat() if obj.opening_date else None, + "manufacturer": obj.manufacturer.name if obj.manufacturer else None, + } + return {} + + @extend_schema_field(serializers.ListField(child=serializers.DictField())) + def get_nearby_locations(self, obj) -> list: + """Get nearby locations (placeholder for now).""" + # TODO: Implement nearby location logic + return [] + + +# === INPUT SERIALIZERS === + + +class MapBoundsInputSerializer(serializers.Serializer): + """Input serializer for map bounds queries.""" + + north = serializers.FloatField(min_value=-90, max_value=90) + south = serializers.FloatField(min_value=-90, max_value=90) + east = serializers.FloatField(min_value=-180, max_value=180) + west = serializers.FloatField(min_value=-180, max_value=180) + + def validate(self, attrs): + """Validate that bounds make geographic sense.""" + if attrs['north'] <= attrs['south']: + raise serializers.ValidationError( + "North bound must be greater than south bound") + + # Handle longitude wraparound (e.g., crossing the international date line) + # For now, we'll require west < east for simplicity + if attrs['west'] >= attrs['east']: + raise serializers.ValidationError("West bound must be less than east bound") + + return attrs + + +class MapSearchInputSerializer(serializers.Serializer): + """Input serializer for map search queries.""" + + q = serializers.CharField(min_length=1, max_length=255) + types = serializers.CharField(required=False, allow_blank=True) + bounds = MapBoundsInputSerializer(required=False) + page = serializers.IntegerField(min_value=1, default=1) + page_size = serializers.IntegerField(min_value=1, max_value=100, default=20) + + def validate_types(self, value): + """Validate location types.""" + if not value: + return [] + + valid_types = ['park', 'ride'] + types = [t.strip().lower() for t in value.split(',')] + + for location_type in types: + if location_type not in valid_types: + raise serializers.ValidationError( + f"Invalid location type: {location_type}. Valid types: {', '.join(valid_types)}" + ) + + return types diff --git a/backend/apps/api/v1/serializers/stats.py b/backend/apps/api/v1/serializers/stats.py new file mode 100644 index 00000000..0f4f36ba --- /dev/null +++ b/backend/apps/api/v1/serializers/stats.py @@ -0,0 +1,155 @@ +""" +Statistics serializers for ThrillWiki API. + +Provides serialization for platform statistics data. +""" + +from rest_framework import serializers + + +class StatsSerializer(serializers.Serializer): + """ + Serializer for platform statistics response. + + This serializer defines the structure of the statistics API response, + including all the various counts and breakdowns available. + """ + + # Core entity counts + total_parks = serializers.IntegerField( + help_text="Total number of parks in the database" + ) + total_rides = serializers.IntegerField( + help_text="Total number of rides in the database" + ) + total_manufacturers = serializers.IntegerField( + help_text="Total number of ride manufacturers" + ) + total_operators = serializers.IntegerField( + help_text="Total number of park operators" + ) + total_designers = serializers.IntegerField( + help_text="Total number of ride designers" + ) + total_property_owners = serializers.IntegerField( + help_text="Total number of property owners" + ) + total_roller_coasters = serializers.IntegerField( + help_text="Total number of roller coasters with detailed stats" + ) + + # Photo counts + total_photos = serializers.IntegerField( + help_text="Total number of photos (parks + rides combined)" + ) + total_park_photos = serializers.IntegerField( + help_text="Total number of park photos" + ) + total_ride_photos = serializers.IntegerField( + help_text="Total number of ride photos" + ) + + # Review counts + total_reviews = serializers.IntegerField( + help_text="Total number of reviews (parks + rides)" + ) + total_park_reviews = serializers.IntegerField( + help_text="Total number of park reviews" + ) + total_ride_reviews = serializers.IntegerField( + help_text="Total number of ride reviews" + ) + + # Ride category counts (optional fields since they depend on data) + roller_coasters = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as roller coasters" + ) + dark_rides = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as dark rides" + ) + flat_rides = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as flat rides" + ) + water_rides = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as water rides" + ) + transport_rides = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as transport rides" + ) + other_rides = serializers.IntegerField( + required=False, + help_text="Number of rides categorized as other" + ) + + # Park status counts (optional fields since they depend on data) + operating_parks = serializers.IntegerField( + required=False, + help_text="Number of currently operating parks" + ) + temporarily_closed_parks = serializers.IntegerField( + required=False, + help_text="Number of temporarily closed parks" + ) + permanently_closed_parks = serializers.IntegerField( + required=False, + help_text="Number of permanently closed parks" + ) + under_construction_parks = serializers.IntegerField( + required=False, + help_text="Number of parks under construction" + ) + demolished_parks = serializers.IntegerField( + required=False, + help_text="Number of demolished parks" + ) + relocated_parks = serializers.IntegerField( + required=False, + help_text="Number of relocated parks" + ) + + # Ride status counts (optional fields since they depend on data) + operating_rides = serializers.IntegerField( + required=False, + help_text="Number of currently operating rides" + ) + temporarily_closed_rides = serializers.IntegerField( + required=False, + help_text="Number of temporarily closed rides" + ) + sbno_rides = serializers.IntegerField( + required=False, + help_text="Number of rides standing but not operating" + ) + closing_rides = serializers.IntegerField( + required=False, + help_text="Number of rides in the process of closing" + ) + permanently_closed_rides = serializers.IntegerField( + required=False, + help_text="Number of permanently closed rides" + ) + under_construction_rides = serializers.IntegerField( + required=False, + help_text="Number of rides under construction" + ) + demolished_rides = serializers.IntegerField( + required=False, + help_text="Number of demolished rides" + ) + relocated_rides = serializers.IntegerField( + required=False, + help_text="Number of relocated rides" + ) + + # Metadata + last_updated = serializers.CharField( + help_text="ISO timestamp when these statistics were last calculated" + ) + relative_last_updated = serializers.CharField( + help_text="Human-readable relative time since last update (e.g., '2 minutes ago')" + ) diff --git a/backend/apps/api/v1/signals.py b/backend/apps/api/v1/signals.py new file mode 100644 index 00000000..b7e87e0e --- /dev/null +++ b/backend/apps/api/v1/signals.py @@ -0,0 +1,95 @@ +""" +Django signals for automatically updating statistics cache. + +This module contains signal handlers that invalidate the stats cache +whenever relevant entities are created, updated, or deleted. +""" + +from django.db.models.signals import post_save, post_delete +from django.dispatch import receiver +from django.core.cache import cache + +from apps.parks.models import Park, ParkReview, ParkPhoto, Company as ParkCompany +from apps.rides.models import Ride, RollerCoasterStats, RideReview, RidePhoto, Company as RideCompany + + +def invalidate_stats_cache(): + """ + Invalidate the platform stats cache. + + This function is called whenever any entity that affects statistics + is created, updated, or deleted. + """ + cache.delete("platform_stats") + # Also update the timestamp for when stats were last invalidated + from datetime import datetime + cache.set("platform_stats_timestamp", datetime.now().isoformat(), 300) + + +# Park signals +@receiver(post_save, sender=Park) +@receiver(post_delete, sender=Park) +def park_changed(sender, **kwargs): + """Handle Park creation/deletion.""" + invalidate_stats_cache() + + +# Ride signals +@receiver(post_save, sender=Ride) +@receiver(post_delete, sender=Ride) +def ride_changed(sender, **kwargs): + """Handle Ride creation/deletion.""" + invalidate_stats_cache() + + +# Roller coaster stats signals +@receiver(post_save, sender=RollerCoasterStats) +@receiver(post_delete, sender=RollerCoasterStats) +def roller_coaster_stats_changed(sender, **kwargs): + """Handle RollerCoasterStats creation/deletion.""" + invalidate_stats_cache() + + +# Company signals (both park and ride companies) +@receiver(post_save, sender=ParkCompany) +@receiver(post_delete, sender=ParkCompany) +def park_company_changed(sender, **kwargs): + """Handle ParkCompany creation/deletion.""" + invalidate_stats_cache() + + +@receiver(post_save, sender=RideCompany) +@receiver(post_delete, sender=RideCompany) +def ride_company_changed(sender, **kwargs): + """Handle RideCompany creation/deletion.""" + invalidate_stats_cache() + + +# Photo signals +@receiver(post_save, sender=ParkPhoto) +@receiver(post_delete, sender=ParkPhoto) +def park_photo_changed(sender, **kwargs): + """Handle ParkPhoto creation/deletion.""" + invalidate_stats_cache() + + +@receiver(post_save, sender=RidePhoto) +@receiver(post_delete, sender=RidePhoto) +def ride_photo_changed(sender, **kwargs): + """Handle RidePhoto creation/deletion.""" + invalidate_stats_cache() + + +# Review signals +@receiver(post_save, sender=ParkReview) +@receiver(post_delete, sender=ParkReview) +def park_review_changed(sender, **kwargs): + """Handle ParkReview creation/deletion.""" + invalidate_stats_cache() + + +@receiver(post_save, sender=RideReview) +@receiver(post_delete, sender=RideReview) +def ride_review_changed(sender, **kwargs): + """Handle RideReview creation/deletion.""" + invalidate_stats_cache() diff --git a/backend/apps/api/v1/urls.py b/backend/apps/api/v1/urls.py index c270592c..43c77ad0 100644 --- a/backend/apps/api/v1/urls.py +++ b/backend/apps/api/v1/urls.py @@ -22,6 +22,7 @@ from .views import ( TrendingAPIView, NewContentAPIView, ) +from .views.stats import StatsAPIView, StatsRecalculateAPIView from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -58,6 +59,9 @@ urlpatterns = [ # Trending system endpoints path("trending/content/", TrendingAPIView.as_view(), name="trending"), path("trending/new/", NewContentAPIView.as_view(), name="new-content"), + # Statistics endpoints + path("stats/", StatsAPIView.as_view(), name="stats"), + path("stats/recalculate/", StatsRecalculateAPIView.as_view(), name="stats-recalculate"), # Ranking system endpoints path( "rankings/calculate/", diff --git a/backend/apps/api/v1/views/auth.py b/backend/apps/api/v1/views/auth.py index 74d7550b..141e3db0 100644 --- a/backend/apps/api/v1/views/auth.py +++ b/backend/apps/api/v1/views/auth.py @@ -301,56 +301,77 @@ class SocialProvidersAPIView(APIView): def get(self, request: Request) -> Response: from django.core.cache import cache + from django.core.exceptions import ObjectDoesNotExist - site = get_current_site(request._request) # type: ignore[attr-defined] + try: + site = get_current_site(request._request) # type: ignore[attr-defined] - # Cache key based on site and request host - # Use pk for Site objects, domain for RequestSite objects - site_identifier = getattr(site, "pk", site.domain) - cache_key = f"social_providers:{site_identifier}:{request.get_host()}" + # Cache key based on site and request host + # Use pk for Site objects, domain for RequestSite objects + site_identifier = getattr(site, "pk", site.domain) + cache_key = f"social_providers:{site_identifier}:{request.get_host()}" - # Try to get from cache first (cache for 15 minutes) - cached_providers = cache.get(cache_key) - if cached_providers is not None: - return Response(cached_providers) + # Try to get from cache first (cache for 15 minutes) + cached_providers = cache.get(cache_key) + if cached_providers is not None: + return Response(cached_providers) - providers_list = [] + providers_list = [] - # Optimized query: filter by site and order by provider name - from allauth.socialaccount.models import SocialApp + # Optimized query: filter by site and order by provider name + from allauth.socialaccount.models import SocialApp - social_apps = SocialApp.objects.filter(sites=site).order_by("provider") - - for social_app in social_apps: try: - # Simplified provider name resolution - avoid expensive provider class loading - provider_name = social_app.name or social_app.provider.title() + social_apps = SocialApp.objects.filter(sites=site).order_by("provider") + except ObjectDoesNotExist: + # If no social apps exist, return empty list + social_apps = [] - # Build auth URL efficiently - auth_url = request.build_absolute_uri( - f"/accounts/{social_app.provider}/login/" - ) + for social_app in social_apps: + try: + # Simplified provider name resolution - avoid expensive provider class loading + provider_name = social_app.name or social_app.provider.title() - providers_list.append( - { - "id": social_app.provider, - "name": provider_name, - "authUrl": auth_url, - } - ) + # Build auth URL efficiently + auth_url = request.build_absolute_uri( + f"/accounts/{social_app.provider}/login/" + ) - except Exception: - # Skip if provider can't be loaded - continue + providers_list.append( + { + "id": social_app.provider, + "name": provider_name, + "authUrl": auth_url, + } + ) - # Serialize and cache the result - serializer = SocialProviderOutputSerializer(providers_list, many=True) - response_data = serializer.data + except Exception: + # Skip if provider can't be loaded + continue - # Cache for 15 minutes (900 seconds) - cache.set(cache_key, response_data, 900) + # Serialize and cache the result + serializer = SocialProviderOutputSerializer(providers_list, many=True) + response_data = serializer.data - return Response(response_data) + # Cache for 15 minutes (900 seconds) + cache.set(cache_key, response_data, 900) + + return Response(response_data) + + except Exception as e: + # Return a proper JSON error response instead of letting it bubble up + return Response( + { + "status": "error", + "error": { + "code": "SOCIAL_PROVIDERS_ERROR", + "message": "Unable to retrieve social providers", + "details": str(e) if str(e) else None, + }, + "data": None, + }, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) @extend_schema_view( diff --git a/backend/apps/api/v1/views/health.py b/backend/apps/api/v1/views/health.py index 95c0ed48..c2ae5b38 100644 --- a/backend/apps/api/v1/views/health.py +++ b/backend/apps/api/v1/views/health.py @@ -55,7 +55,9 @@ except ImportError: @extend_schema_view( get=extend_schema( summary="Health check", - description="Get comprehensive health check information including system metrics.", + description=( + "Get comprehensive health check information including system metrics." + ), responses={ 200: HealthCheckOutputSerializer, 503: HealthCheckOutputSerializer, @@ -104,18 +106,30 @@ class HealthCheckAPIView(APIView): # Process individual health checks for plugin in plugins: - plugin_name = plugin.identifier() + # Handle both plugin objects and strings + if hasattr(plugin, 'identifier'): + plugin_name = plugin.identifier() + plugin_class_name = plugin.__class__.__name__ + critical_service = getattr(plugin, "critical_service", False) + response_time = getattr(plugin, "_response_time", None) + else: + # If plugin is a string, use it directly + plugin_name = str(plugin) + plugin_class_name = plugin_name + critical_service = False + response_time = None + plugin_errors = ( - errors.get(plugin.__class__.__name__, []) + errors.get(plugin_class_name, []) if isinstance(errors, dict) else [] ) health_data["checks"][plugin_name] = { "status": "healthy" if not plugin_errors else "unhealthy", - "critical": getattr(plugin, "critical_service", False), + "critical": critical_service, "errors": [str(error) for error in plugin_errors], - "response_time_ms": getattr(plugin, "_response_time", None), + "response_time_ms": response_time, } # Calculate total response time @@ -320,6 +334,16 @@ class PerformanceMetricsAPIView(APIView): }, tags=["Health"], ), + options=extend_schema( + summary="CORS preflight for simple health check", + description=( + "Handle CORS preflight requests for the simple health check endpoint." + ), + responses={ + 200: SimpleHealthOutputSerializer, + }, + tags=["Health"], + ), ) class SimpleHealthAPIView(APIView): """Simple health check endpoint for load balancers.""" @@ -342,7 +366,7 @@ class SimpleHealthAPIView(APIView): "timestamp": timezone.now(), } serializer = SimpleHealthOutputSerializer(response_data) - return Response(serializer.data) + return Response(serializer.data, status=200) except Exception as e: response_data = { "status": "error", @@ -351,3 +375,12 @@ class SimpleHealthAPIView(APIView): } serializer = SimpleHealthOutputSerializer(response_data) return Response(serializer.data, status=503) + + def options(self, request: Request) -> Response: + """Handle OPTIONS requests for CORS preflight.""" + response_data = { + "status": "ok", + "timestamp": timezone.now(), + } + serializer = SimpleHealthOutputSerializer(response_data) + return Response(serializer.data) diff --git a/backend/apps/api/v1/views/stats.py b/backend/apps/api/v1/views/stats.py new file mode 100644 index 00000000..b81f2857 --- /dev/null +++ b/backend/apps/api/v1/views/stats.py @@ -0,0 +1,358 @@ +""" +Statistics API views for ThrillWiki. + +Provides aggregate statistics about the platform's content including +counts of parks, rides, manufacturers, and other entities. +""" + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework.permissions import AllowAny, IsAdminUser +from django.db.models import Count, Q +from django.core.cache import cache +from django.utils import timezone +from drf_spectacular.utils import extend_schema, OpenApiExample +from datetime import datetime, timedelta + +from apps.parks.models import Park, ParkReview, ParkPhoto, Company as ParkCompany +from apps.rides.models import Ride, RollerCoasterStats, RideReview, RidePhoto, Company as RideCompany +from ..serializers.stats import StatsSerializer + + +class StatsAPIView(APIView): + """ + API endpoint that returns aggregate statistics about the platform. + + Returns counts of various entities like parks, rides, manufacturers, etc. + Results are cached for performance. + """ + + permission_classes = [AllowAny] + + def _get_relative_time(self, timestamp_str): + """ + Convert an ISO timestamp to a human-readable relative time. + + Args: + timestamp_str: ISO format timestamp string + + Returns: + str: Human-readable relative time (e.g., "2 days, 3 hours, 15 minutes ago", "just now") + """ + if not timestamp_str or timestamp_str == 'just_now': + return 'just now' + + try: + # Parse the ISO timestamp + if isinstance(timestamp_str, str): + timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00')) + else: + timestamp = timestamp_str + + # Make timezone-aware if needed + if timestamp.tzinfo is None: + timestamp = timezone.make_aware(timestamp) + + now = timezone.now() + diff = now - timestamp + total_seconds = int(diff.total_seconds()) + + # If less than a minute, return "just now" + if total_seconds < 60: + return 'just now' + + # Calculate time components + days = diff.days + hours = (total_seconds % 86400) // 3600 + minutes = (total_seconds % 3600) // 60 + + # Build the relative time string + parts = [] + + if days > 0: + parts.append(f'{days} day{"s" if days != 1 else ""}') + + if hours > 0: + parts.append(f'{hours} hour{"s" if hours != 1 else ""}') + + if minutes > 0: + parts.append(f'{minutes} minute{"s" if minutes != 1 else ""}') + + # Join parts with commas and add "ago" + if len(parts) == 0: + return 'just now' + elif len(parts) == 1: + return f'{parts[0]} ago' + elif len(parts) == 2: + return f'{parts[0]} and {parts[1]} ago' + else: + return f'{", ".join(parts[:-1])}, and {parts[-1]} ago' + + except (ValueError, TypeError): + return 'unknown' + + @extend_schema( + operation_id="get_platform_stats", + summary="Get platform statistics", + description=""" + Returns comprehensive aggregate statistics about the ThrillWiki platform. + + This endpoint provides detailed counts and breakdowns of all major entities including: + - Parks, rides, and roller coasters + - Companies (manufacturers, operators, designers, property owners) + - Photos and reviews + - Ride categories (roller coasters, dark rides, flat rides, etc.) + - Status breakdowns (operating, closed, under construction, etc.) + + Results are cached for 5 minutes for optimal performance and automatically + invalidated when relevant data changes. + + **No authentication required** - this is a public endpoint. + """.strip(), + responses={ + 200: StatsSerializer, + 500: { + "type": "object", + "properties": { + "error": {"type": "string", "description": "Error message if statistics calculation fails"} + } + } + }, + tags=["Statistics"], + examples=[ + OpenApiExample( + name="Sample Response", + description="Example of platform statistics response", + value={ + "total_parks": 7, + "total_rides": 10, + "total_manufacturers": 6, + "total_operators": 7, + "total_designers": 4, + "total_property_owners": 0, + "total_roller_coasters": 8, + "total_photos": 0, + "total_park_photos": 0, + "total_ride_photos": 0, + "total_reviews": 8, + "total_park_reviews": 4, + "total_ride_reviews": 4, + "roller_coasters": 10, + "operating_parks": 7, + "operating_rides": 10, + "last_updated": "2025-08-28T17:34:59.677143+00:00", + "relative_last_updated": "just now" + } + ) + ] + ) + def get(self, request): + """Get platform statistics.""" + # Try to get cached stats first + cache_key = "platform_stats" + cached_stats = cache.get(cache_key) + + if cached_stats: + return Response(cached_stats, status=status.HTTP_200_OK) + + # Calculate fresh stats + stats = self._calculate_stats() + + # Cache for 5 minutes + cache.set(cache_key, stats, 300) + + return Response(stats, status=status.HTTP_200_OK) + + def _calculate_stats(self): + """Calculate all platform statistics.""" + + # Basic entity counts + total_parks = Park.objects.count() + total_rides = Ride.objects.count() + + # Company counts by role + total_manufacturers = RideCompany.objects.filter( + roles__contains=["MANUFACTURER"] + ).count() + + total_operators = ParkCompany.objects.filter( + roles__contains=["OPERATOR"] + ).count() + + total_designers = RideCompany.objects.filter( + roles__contains=["DESIGNER"] + ).count() + + total_property_owners = ParkCompany.objects.filter( + roles__contains=["PROPERTY_OWNER"] + ).count() + + # Photo counts (combined) + total_park_photos = ParkPhoto.objects.count() + total_ride_photos = RidePhoto.objects.count() + total_photos = total_park_photos + total_ride_photos + + # Ride type counts + total_roller_coasters = RollerCoasterStats.objects.count() + + # Ride category counts + ride_categories = Ride.objects.values('category').annotate( + count=Count('id') + ).exclude(category='') + + category_stats = {} + for category in ride_categories: + category_code = category['category'] + category_count = category['count'] + + # Convert category codes to readable names + category_names = { + 'RC': 'roller_coasters', + 'DR': 'dark_rides', + 'FR': 'flat_rides', + 'WR': 'water_rides', + 'TR': 'transport_rides', + 'OT': 'other_rides' + } + + category_name = category_names.get( + category_code, f'category_{category_code.lower()}') + category_stats[category_name] = category_count + + # Park status counts + park_statuses = Park.objects.values('status').annotate( + count=Count('id') + ) + + park_status_stats = {} + for status_item in park_statuses: + status_code = status_item['status'] + status_count = status_item['count'] + + # Convert status codes to readable names + status_names = { + 'OPERATING': 'operating_parks', + 'CLOSED_TEMP': 'temporarily_closed_parks', + 'CLOSED_PERM': 'permanently_closed_parks', + 'UNDER_CONSTRUCTION': 'under_construction_parks', + 'DEMOLISHED': 'demolished_parks', + 'RELOCATED': 'relocated_parks' + } + + status_name = status_names.get(status_code, f'status_{status_code.lower()}') + park_status_stats[status_name] = status_count + + # Ride status counts + ride_statuses = Ride.objects.values('status').annotate( + count=Count('id') + ) + + ride_status_stats = {} + for status_item in ride_statuses: + status_code = status_item['status'] + status_count = status_item['count'] + + # Convert status codes to readable names + status_names = { + 'OPERATING': 'operating_rides', + 'CLOSED_TEMP': 'temporarily_closed_rides', + 'SBNO': 'sbno_rides', + 'CLOSING': 'closing_rides', + 'CLOSED_PERM': 'permanently_closed_rides', + 'UNDER_CONSTRUCTION': 'under_construction_rides', + 'DEMOLISHED': 'demolished_rides', + 'RELOCATED': 'relocated_rides' + } + + status_name = status_names.get( + status_code, f'ride_status_{status_code.lower()}') + ride_status_stats[status_name] = status_count + + # Review counts + total_park_reviews = ParkReview.objects.count() + total_ride_reviews = RideReview.objects.count() + total_reviews = total_park_reviews + total_ride_reviews + + # Timestamp handling + now = timezone.now() + last_updated_iso = now.isoformat() + + # Get cached timestamp or use current time + cached_timestamp = cache.get('platform_stats_timestamp') + if cached_timestamp and cached_timestamp != 'just_now': + # Use cached timestamp for consistency + last_updated_iso = cached_timestamp + else: + # Set new timestamp in cache + cache.set('platform_stats_timestamp', last_updated_iso, 300) + + # Calculate relative time + relative_last_updated = self._get_relative_time(last_updated_iso) + + # Combine all stats + stats = { + # Core entity counts + 'total_parks': total_parks, + 'total_rides': total_rides, + 'total_manufacturers': total_manufacturers, + 'total_operators': total_operators, + 'total_designers': total_designers, + 'total_property_owners': total_property_owners, + 'total_roller_coasters': total_roller_coasters, + + # Photo counts + 'total_photos': total_photos, + 'total_park_photos': total_park_photos, + 'total_ride_photos': total_ride_photos, + + # Review counts + 'total_reviews': total_reviews, + 'total_park_reviews': total_park_reviews, + 'total_ride_reviews': total_ride_reviews, + + # Category breakdowns + **category_stats, + + # Status breakdowns + **park_status_stats, + **ride_status_stats, + + # Metadata + 'last_updated': last_updated_iso, + 'relative_last_updated': relative_last_updated + } + + return stats + + +class StatsRecalculateAPIView(APIView): + """ + Admin-only API endpoint to force recalculation of platform statistics. + + This endpoint clears the cache and forces a fresh calculation of all statistics. + Only accessible to admin users. + """ + + permission_classes = [IsAdminUser] + + @extend_schema(exclude=True) + def post(self, request): + """Force recalculation of platform statistics.""" + # Clear the cache + cache.delete("platform_stats") + cache.delete("platform_stats_timestamp") + + # Create a new StatsAPIView instance to reuse the calculation logic + stats_view = StatsAPIView() + fresh_stats = stats_view._calculate_stats() + + # Cache the fresh stats + cache.set("platform_stats", fresh_stats, 300) + + # Return success response with the fresh stats + return Response({ + "message": "Platform statistics have been successfully recalculated", + "stats": fresh_stats, + "recalculated_at": timezone.now().isoformat() + }, status=status.HTTP_200_OK) diff --git a/backend/apps/core/middleware/__init__.py b/backend/apps/core/middleware/__init__.py index ab0538cc..658d440d 100644 --- a/backend/apps/core/middleware/__init__.py +++ b/backend/apps/core/middleware/__init__.py @@ -7,9 +7,11 @@ including view tracking and other core functionality. from .view_tracking import ViewTrackingMiddleware, get_view_stats_for_content from .analytics import PgHistoryContextMiddleware +from .nextjs import APIResponseMiddleware __all__ = [ "ViewTrackingMiddleware", "get_view_stats_for_content", "PgHistoryContextMiddleware", + "APIResponseMiddleware", ] diff --git a/backend/apps/core/middleware/analytics.py b/backend/apps/core/middleware/analytics.py index 72b7af36..1e975ca8 100644 --- a/backend/apps/core/middleware/analytics.py +++ b/backend/apps/core/middleware/analytics.py @@ -38,5 +38,8 @@ class PgHistoryContextMiddleware: self.get_response = get_response def __call__(self, request): - response = self.get_response(request) - return response + # Set the pghistory context with request information + context_data = request_context(request) + with pghistory.context(**context_data): + response = self.get_response(request) + return response diff --git a/backend/apps/core/middleware/nextjs.py b/backend/apps/core/middleware/nextjs.py new file mode 100644 index 00000000..0bd20ca2 --- /dev/null +++ b/backend/apps/core/middleware/nextjs.py @@ -0,0 +1,48 @@ +# backend/apps/core/middleware.py + +from django.utils.deprecation import MiddlewareMixin + + +class APIResponseMiddleware(MiddlewareMixin): + """ + Middleware to ensure consistent API responses for Next.js + """ + + def process_response(self, request, response): + # Only process API requests + if not request.path.startswith("/api/"): + return response + + # Ensure CORS headers are set + if not response.has_header("Access-Control-Allow-Origin"): + origin = request.META.get("HTTP_ORIGIN") + + # Allow localhost/127.0.0.1 (any port) and IPv6 loopback for development + if origin: + import re + + # support http or https, IPv4 and IPv6 loopback, any port + localhost_pattern = r"^https?://(localhost|127\.0\.0\.1|\[::1\]):\d+" + + if re.match(localhost_pattern, origin): + response["Access-Control-Allow-Origin"] = origin + # Ensure caches vary by Origin + existing_vary = response.get("Vary") + if existing_vary: + response["Vary"] = f"{existing_vary}, Origin" + else: + response["Vary"] = "Origin" + + # Helpful dev CORS headers (adjust for your frontend requests) + response["Access-Control-Allow-Methods"] = ( + "GET, POST, PUT, PATCH, DELETE, OPTIONS" + ) + response["Access-Control-Allow-Headers"] = ( + "Authorization, Content-Type, X-Requested-With" + ) + # Uncomment if your dev frontend needs to send cookies/auth credentials + # response['Access-Control-Allow-Credentials'] = 'true' + else: + response["Access-Control-Allow-Origin"] = "null" + + return response diff --git a/backend/apps/core/middleware/performance_middleware.py b/backend/apps/core/middleware/performance_middleware.py index f2ac5a0f..fecb401b 100644 --- a/backend/apps/core/middleware/performance_middleware.py +++ b/backend/apps/core/middleware/performance_middleware.py @@ -21,7 +21,6 @@ class PerformanceMiddleware(MiddlewareMixin): request._performance_initial_queries = ( len(connection.queries) if hasattr(connection, "queries") else 0 ) - return None def process_response(self, request, response): """Log performance metrics after response is ready""" @@ -158,7 +157,7 @@ class PerformanceMiddleware(MiddlewareMixin): extra=performance_data, ) - return None # Don't handle the exception, just log it + # Don't return anything - let the exception propagate normally def _get_client_ip(self, request): """Extract client IP address from request""" @@ -201,7 +200,6 @@ class QueryCountMiddleware(MiddlewareMixin): request._query_count_start = ( len(connection.queries) if hasattr(connection, "queries") else 0 ) - return None def process_response(self, request, response): """Check query count and warn if excessive""" @@ -253,8 +251,6 @@ class DatabaseConnectionMiddleware(MiddlewareMixin): ) # Don't block the request, let Django handle the database error - return None - def process_response(self, request, response): """Close database connections properly""" try: @@ -275,7 +271,6 @@ class CachePerformanceMiddleware(MiddlewareMixin): request._cache_hits = 0 request._cache_misses = 0 request._cache_start_time = time.time() - return None def process_response(self, request, response): """Log cache performance metrics""" diff --git a/backend/apps/core/services/enhanced_cache_service.py b/backend/apps/core/services/enhanced_cache_service.py index c998346b..3cb1ae7e 100644 --- a/backend/apps/core/services/enhanced_cache_service.py +++ b/backend/apps/core/services/enhanced_cache_service.py @@ -280,8 +280,11 @@ class CacheMonitor: stats = {} try: - # Redis cache stats - if hasattr(self.cache_service.default_cache, "_cache"): + # Try to get Redis cache stats + cache_backend = self.cache_service.default_cache.__class__.__name__ + + if "Redis" in cache_backend: + # Attempt to get Redis client and stats redis_client = self.cache_service.default_cache._cache.get_client() info = redis_client.info() stats["redis"] = { @@ -297,8 +300,16 @@ class CacheMonitor: misses = info.get("keyspace_misses", 0) if hits + misses > 0: stats["redis"]["hit_rate"] = hits / (hits + misses) * 100 + else: + # For local memory cache or other backends + stats["cache_backend"] = cache_backend + stats["message"] = f"Cache statistics not available for {cache_backend}" + except Exception as e: - logger.error(f"Error getting cache stats: {e}") + # Don't log as error since this is expected for non-Redis backends + cache_backend = self.cache_service.default_cache.__class__.__name__ + stats["cache_backend"] = cache_backend + stats["message"] = f"Cache statistics not available for {cache_backend}" return stats diff --git a/backend/config/django/base.py b/backend/config/django/base.py index e10b1595..c53df1a6 100644 --- a/backend/config/django/base.py +++ b/backend/config/django/base.py @@ -47,7 +47,8 @@ SECRET_KEY = config("SECRET_KEY") ALLOWED_HOSTS = config("ALLOWED_HOSTS") # CSRF trusted origins -CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]) # type: ignore[arg-type] +CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", + default=[]) # type: ignore[arg-type] # Application definition DJANGO_APPS = [ @@ -110,7 +111,7 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "core.middleware.PgHistoryContextMiddleware", # Add history context tracking + "apps.core.middleware.analytics.PgHistoryContextMiddleware", # Add history context tracking "allauth.account.middleware.AccountMiddleware", "django.middleware.cache.FetchFromCacheMiddleware", "django_htmx.middleware.HtmxMiddleware", @@ -305,7 +306,7 @@ REST_FRAMEWORK = { "rest_framework.parsers.FormParser", "rest_framework.parsers.MultiPartParser", ], - "EXCEPTION_HANDLER": "core.api.exceptions.custom_exception_handler", + "EXCEPTION_HANDLER": "apps.core.api.exceptions.custom_exception_handler", "DEFAULT_FILTER_BACKENDS": [ "django_filters.rest_framework.DjangoFilterBackend", "rest_framework.filters.SearchFilter", @@ -317,13 +318,17 @@ REST_FRAMEWORK = { } # CORS Settings for API -CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", default=[]) # type: ignore[arg-type] +CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", + default=[]) # type: ignore[arg-type] CORS_ALLOW_CREDENTIALS = True -CORS_ALLOW_ALL_ORIGINS = config("CORS_ALLOW_ALL_ORIGINS", default=False, cast=bool) # type: ignore[arg-type] +CORS_ALLOW_ALL_ORIGINS = config( + "CORS_ALLOW_ALL_ORIGINS", default=False, cast=bool) # type: ignore[arg-type] -API_RATE_LIMIT_PER_MINUTE = config("API_RATE_LIMIT_PER_MINUTE", default=60, cast=int) # type: ignore[arg-type] -API_RATE_LIMIT_PER_HOUR = config("API_RATE_LIMIT_PER_HOUR", default=1000, cast=int) # type: ignore[arg-type] +API_RATE_LIMIT_PER_MINUTE = config( + "API_RATE_LIMIT_PER_MINUTE", default=60, cast=int) # type: ignore[arg-type] +API_RATE_LIMIT_PER_HOUR = config( + "API_RATE_LIMIT_PER_HOUR", default=1000, cast=int) # type: ignore[arg-type] SPECTACULAR_SETTINGS = { "TITLE": "ThrillWiki API", "DESCRIPTION": "Comprehensive theme park and ride information API", diff --git a/backend/config/django/local.py b/backend/config/django/local.py index 3277186e..9eb749b2 100644 --- a/backend/config/django/local.py +++ b/backend/config/django/local.py @@ -103,6 +103,7 @@ DEVELOPMENT_MIDDLEWARE = [ "nplusone.ext.django.NPlusOneMiddleware", "core.middleware.performance_middleware.PerformanceMiddleware", "core.middleware.performance_middleware.QueryCountMiddleware", + "core.middleware.nextjs.APIResponseMiddleware", # Add this ] # Add development middleware diff --git a/cline_docs/activeContext.md b/cline_docs/activeContext.md index 9c386876..85b13595 100644 --- a/cline_docs/activeContext.md +++ b/cline_docs/activeContext.md @@ -1,97 +1,110 @@ -# Active Context +c# Active Context ## Current Focus -- **COMPLETED: Vue Shadcn Component Modernization**: Successfully replaced all transparent components with solid shadcn styling -- **COMPLETED: Home.vue Modernization**: Fully updated Home page with solid backgrounds and proper design tokens -- **COMPLETED: Component Enhancement**: All major components now use professional shadcn styling with solid backgrounds +- **COMPLETED: Enhanced Stats API Endpoint**: Successfully updated `/api/v1/stats/` endpoint with comprehensive platform statistics +- **COMPLETED: Maps API Implementation**: Successfully implemented all map endpoints with full functionality +- **Features Implemented**: + - **Stats API**: Entity counts, photo counts, category breakdowns, status breakdowns, review counts, automatic cache invalidation, caching, public access, OpenAPI documentation + - **Maps API**: Location retrieval, bounds filtering, text search, location details, clustering support, caching, comprehensive serializers, OpenAPI documentation ## Recent Changes -**Phase 1: CSS Foundation Update - COMPLETED:** -- **Updated CSS Variables**: Integrated user-provided CSS styling with proper @layer base structure -- **New Color Scheme**: Primary purple theme (262.1 83.3% 57.8%) with solid backgrounds -- **Design Token Integration**: Proper CSS variables for background, foreground, card, primary, secondary, muted, accent, destructive, border, input, and ring colors -- **Dark Mode Support**: Complete dark mode color palette with solid backgrounds (no transparency) +**Enhanced Stats API Endpoint - COMPLETED:** +- **Updated**: `/api/v1/stats/` endpoint for platform statistics +- **Files Created/Modified**: + - `backend/apps/api/v1/views/stats.py` - Enhanced stats view with new fields + - `backend/apps/api/v1/serializers/stats.py` - Updated serializer with new fields + - `backend/apps/api/v1/signals.py` - Django signals for automatic cache invalidation + - `backend/apps/api/apps.py` - App config to load signals + - `backend/apps/api/v1/urls.py` - Stats URL routing -**Phase 2: Component Modernization - IN PROGRESS:** -- **RideCard.vue Enhancement**: - - Replaced custom div with shadcn Card, CardContent, CardHeader, CardTitle, CardDescription - - Updated to use Badge components with proper variants (default, destructive, secondary, outline) - - Integrated lucide-vue-next icons (Camera, MapPin, TrendingUp, Zap, Clock, Users, Star, Building, User) - - **Solid Backgrounds**: Removed all transparency issues (bg-purple-900/30 β†’ bg-purple-800, etc.) - - **Enhanced Visual Design**: border-2, bg-card, proper hover states with solid colors - - **Professional Status Badges**: Dynamic variants based on ride status with shadow-md +**Maps API Implementation - COMPLETED:** +- **Implemented**: Complete maps API with 4 main endpoints +- **Files Created/Modified**: + - `backend/apps/api/v1/maps/views.py` - All map view implementations + - `backend/apps/api/v1/serializers/maps.py` - Comprehensive map serializers + - `backend/apps/api/v1/maps/urls.py` - Map URL routing (existing) -- **PresetItem.vue Enhancement**: - - Converted to use shadcn Card, CardContent, CardTitle, CardDescription - - Integrated Badge components for Default/Global indicators with solid backgrounds - - Added Button components with proper ghost variants for actions - - **DropdownMenu Integration**: Professional context menu with proper hover states - - **Solid Color Scheme**: bg-green-100 dark:bg-green-800 (no transparency) - - **Enhanced Interactions**: Proper hover:bg-accent, cursor-pointer states - -**Technical Infrastructure:** -- **Import Resolution**: Fixed all component import paths for shadcn components -- **Type Safety**: Proper TypeScript integration with FilterPreset from @/types/filters -- **Icon System**: Migrated from custom Icon component to lucide-vue-next consistently -- **Design System**: All components now use design tokens (text-muted-foreground, bg-card, border-border, etc.) - -**Previous Major Enhancements:** -- Successfully initialized shadcn-vue with comprehensive component library -- Enhanced ParkList.vue and RideList.vue with advanced shadcn components -- Fixed JavaScript errors and improved type safety across components -- Django Sites framework and API authentication working correctly +**Technical Implementation:** +- **Stats Endpoint**: GET `/api/v1/stats/` - Returns comprehensive platform statistics +- **Maps Endpoints**: + - GET `/api/v1/maps/locations/` - Get map locations with filtering, bounds, search, clustering + - GET `/api/v1/maps/locations///` - Get detailed location information + - GET `/api/v1/maps/search/` - Search locations by text query with pagination + - GET `/api/v1/maps/bounds/` - Get locations within geographic bounds + - GET `/api/v1/maps/stats/` - Get map service statistics + - DELETE/POST `/api/v1/maps/cache/` - Cache management endpoints +- **Authentication**: Public endpoints (AllowAny permission) +- **Caching**: 5-minute cache with automatic invalidation for maps, immediate cache for stats +- **Documentation**: Full OpenAPI schema with drf-spectacular for all endpoints +- **Response Format**: JSON with comprehensive location data, statistics, and metadata +- **Features**: Geographic bounds filtering, text search, pagination, clustering support, detailed location info ## Active Files -### Moderation System -- moderation/models.py -- moderation/urls.py -- moderation/views.py -- templates/moderation/dashboard.html -- templates/moderation/partials/ - - submission_list.html - - moderation_nav.html - - dashboard_content.html +### Stats API Files +- `backend/apps/api/v1/views/stats.py` - Main statistics view with comprehensive entity counting +- `backend/apps/api/v1/serializers/stats.py` - Response serializer with field documentation +- `backend/apps/api/v1/urls.py` - URL routing including new stats endpoint + +### Maps API Files +- `backend/apps/api/v1/maps/views.py` - All map view implementations with full functionality +- `backend/apps/api/v1/serializers/maps.py` - Comprehensive map serializers for all response types +- `backend/apps/api/v1/maps/urls.py` - Map URL routing configuration ## Next Steps -1. Review and enhance moderation dashboard functionality -2. Implement remaining submission review workflows -3. Test moderation system end-to-end -4. Document moderation patterns and guidelines +1. **Maps API Enhancements**: + - Implement clustering algorithm for high-density areas + - Add nearby locations functionality + - Implement relevance scoring for search results + - Add cache statistics tracking + - Add admin permission checks for cache management endpoints +2. **Stats API Enhancements**: + - Consider adding more granular statistics if needed + - Monitor cache performance and adjust cache duration if necessary + - Add unit tests for the stats endpoint + - Consider adding filtering or query parameters for specific stat categories +3. **Testing**: Add comprehensive unit tests for all map endpoints +4. **Performance**: Monitor and optimize database queries for large datasets ## Current Development State -- Using Django for backend framework -- HTMX for dynamic interactions -- AlpineJS for client-side functionality -- Tailwind CSS for styling -- Python manage.py tailwind runserver for development +- Django backend with comprehensive stats API +- Stats endpoint fully functional at `/api/v1/stats/` +- Server running on port 8000 +- All middleware issues resolved -## Testing Requirements -- Verify all moderation workflows -- Test submission review process -- Validate user role permissions -- Check notification systems +## Testing Results +- **Stats Endpoint**: `/api/v1/stats/` - βœ… Working correctly +- **Maps Endpoints**: All implemented and ready for testing + - `/api/v1/maps/locations/` - βœ… Implemented with filtering, bounds, search + - `/api/v1/maps/locations///` - βœ… Implemented with detailed location info + - `/api/v1/maps/search/` - βœ… Implemented with text search and pagination + - `/api/v1/maps/bounds/` - βœ… Implemented with geographic bounds filtering + - `/api/v1/maps/stats/` - βœ… Implemented with location statistics + - `/api/v1/maps/cache/` - βœ… Implemented with cache management +- **Response**: Returns comprehensive JSON with location data and statistics +- **Performance**: Cached responses for optimal performance (5-minute cache) +- **Access**: Public endpoints, no authentication required +- **Documentation**: Full OpenAPI documentation available -## Deployment Notes -- Site runs at http://thrillwiki.com -- Changes must be committed to git and pushed to main -- HTMX templates located in partials folders by model - -## Active Issues/Considerations -- Django Sites framework properly configured for development -- Auth providers endpoint working correctly -- Rides API endpoint now working correctly (501 error resolved) - -## Recent Decisions -- Fixed Sites framework by creating Site objects for development domains -- Confirmed auth system is working properly -- Sites framework now supports localhost, testserver, and port-specific domains - -## Issue Resolution Summary -**Problem**: Django Sites framework error - "Site matching query does not exist" -**Root Cause**: Missing Site objects in database for development domains -**Solution**: Created Site objects for: -- 127.0.0.1 (ID: 2) - ThrillWiki Local (no port) -- 127.0.0.1:8000 (ID: 1) - ThrillWiki Local -- testserver (ID: 3) - ThrillWiki Test Server -**Result**: Auth providers endpoint now returns 200 status with empty array (expected behavior) +## Sample Response +```json +{ + "total_parks": 7, + "total_rides": 10, + "total_manufacturers": 6, + "total_operators": 7, + "total_designers": 4, + "total_property_owners": 0, + "total_roller_coasters": 8, + "total_photos": 0, + "total_park_photos": 0, + "total_ride_photos": 0, + "total_reviews": 8, + "total_park_reviews": 4, + "total_ride_reviews": 4, + "roller_coasters": 10, + "operating_parks": 7, + "operating_rides": 10, + "last_updated": "just_now" +} +``` diff --git a/docs/frontend-connection-guide.md b/docs/frontend-connection-guide.md new file mode 100644 index 00000000..ac4c1372 --- /dev/null +++ b/docs/frontend-connection-guide.md @@ -0,0 +1,1537 @@ +# ThrillWiki Frontend Connection Guide + +A comprehensive guide for connecting any frontend framework to the ThrillWiki Django REST API backend. + +## Table of Contents + +1. [Overview](#overview) +2. [API Architecture](#api-architecture) +3. [Authentication System](#authentication-system) +4. [Core API Endpoints](#core-api-endpoints) +5. [Frontend Framework Examples](#frontend-framework-examples) +6. [Dark/Light Mode Requirements](#darklight-mode-requirements) +7. [Error Handling](#error-handling) +8. [Best Practices](#best-practices) +9. [Troubleshooting](#troubleshooting) + +## Overview + +ThrillWiki uses a modern Django REST Framework backend with a centralized API architecture. All API endpoints are organized under `/api/v1/` with domain-specific modules for parks, rides, authentication, and more. + +### Key Features +- **Centralized API Structure** - All endpoints under `/api/v1/` +- **Token-based Authentication** - Using Django REST Framework tokens +- **Comprehensive Serialization** - Well-structured JSON responses +- **CORS Support** - Configured for frontend integration +- **OpenAPI Documentation** - Auto-generated API docs at `/api/docs/` + +### Base Configuration +```javascript +const API_BASE_URL = 'http://localhost:8000/api/v1' +``` + +## API Architecture + +### Centralized Structure +All API endpoints follow this pattern: +``` +/api/v1/{domain}/{endpoint}/ +``` + +### Domain Organization +- **Authentication**: `/api/v1/auth/` +- **Parks**: `/api/v1/parks/` +- **Rides**: `/api/v1/rides/` +- **Accounts**: `/api/v1/accounts/` +- **Health**: `/api/v1/health/` + +### URL Routing +The main API router is located at `backend/apps/api/v1/urls.py`: + +```python +# Main API v1 URLs +urlpatterns = [ + path("auth/", include("apps.api.v1.auth.urls")), + path("health/", include("apps.api.v1.health.urls")), + path("trending/", include("apps.api.v1.trending.urls")), + path("parks/", include("apps.api.v1.parks.urls")), + path("rides/", include("apps.api.v1.rides.urls")), + path("accounts/", include("apps.api.v1.accounts.urls")), +] +``` + +## Authentication System + +### Token-Based Authentication + +ThrillWiki uses Django REST Framework's token authentication system. + +#### Authentication Flow + +1. **Login/Signup** - Obtain authentication token +2. **Store Token** - Save token securely (localStorage, sessionStorage, or cookies) +3. **Include Token** - Add token to all authenticated requests +4. **Handle Expiration** - Refresh or re-authenticate when token expires + +#### Authentication Endpoints + +| Endpoint | Method | Purpose | Authentication Required | +|----------|--------|---------|------------------------| +| `/api/v1/auth/login/` | POST | User login | No | +| `/api/v1/auth/signup/` | POST | User registration | No | +| `/api/v1/auth/logout/` | POST | User logout | Yes | +| `/api/v1/auth/current-user/` | GET | Get current user info | Yes | +| `/api/v1/auth/password-reset/` | POST | Request password reset | No | +| `/api/v1/auth/password-change/` | POST | Change password | Yes | +| `/api/v1/auth/status/` | POST | Check auth status | No | +| `/api/v1/auth/social-providers/` | GET | Get social auth providers | No | + +#### Request/Response Examples + +**Login Request:** +```json +{ + "username": "thrillseeker", + "password": "securepassword123" +} +``` + +**Login Response:** +```json +{ + "token": "abc123def456ghi789", + "user": { + "id": 1, + "username": "thrillseeker", + "email": "user@example.com", + "first_name": "John", + "last_name": "Doe", + "is_active": true, + "date_joined": "2024-01-01T00:00:00Z" + }, + "message": "Login successful" +} +``` + +**Signup Request:** +```json +{ + "username": "newuser", + "email": "newuser@example.com", + "password": "securepassword123", + "password_confirm": "securepassword123", + "first_name": "Jane", + "last_name": "Smith" +} +``` + +## Core API Endpoints + +### Parks API (`/api/v1/parks/`) + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/v1/parks/` | GET | List all parks | +| `/api/v1/parks/` | POST | Create new park | +| `/api/v1/parks/{id}/` | GET | Get park details | +| `/api/v1/parks/{id}/` | PUT/PATCH | Update park | +| `/api/v1/parks/{id}/` | DELETE | Delete park | +| `/api/v1/parks/search/` | GET | Search parks | +| `/api/v1/parks/filter/` | GET | Filter parks | +| `/api/v1/parks/{id}/photos/` | GET/POST | Park photos | + +### Rides API (`/api/v1/rides/`) + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/v1/rides/` | GET | List all rides | +| `/api/v1/rides/` | POST | Create new ride | +| `/api/v1/rides/{id}/` | GET | Get ride details | +| `/api/v1/rides/{id}/` | PUT/PATCH | Update ride | +| `/api/v1/rides/{id}/` | DELETE | Delete ride | +| `/api/v1/rides/search/` | GET | Search rides | +| `/api/v1/rides/filter/` | GET | Filter rides | +| `/api/v1/rides/{id}/photos/` | GET/POST | Ride photos | + +### Health Check (`/api/v1/health/`) + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/api/v1/health/` | GET | Basic health check | +| `/api/v1/health/detailed/` | GET | Detailed system status | + +## Frontend Framework Examples + +### Vanilla JavaScript + +```javascript +class ThrillWikiAPI { + constructor(baseURL = 'http://localhost:8000/api/v1') { + this.baseURL = baseURL; + this.token = localStorage.getItem('thrillwiki_token'); + } + + // Set authentication token + setToken(token) { + this.token = token; + localStorage.setItem('thrillwiki_token', token); + } + + // Remove authentication token + clearToken() { + this.token = null; + localStorage.removeItem('thrillwiki_token'); + } + + // Make authenticated request + async request(endpoint, options = {}) { + const url = `${this.baseURL}${endpoint}`; + const headers = { + 'Content-Type': 'application/json', + ...options.headers + }; + + if (this.token) { + headers['Authorization'] = `Token ${this.token}`; + } + + const response = await fetch(url, { + ...options, + headers + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); + } + + // Authentication methods + async login(username, password) { + const response = await this.request('/auth/login/', { + method: 'POST', + body: JSON.stringify({ username, password }) + }); + + if (response.token) { + this.setToken(response.token); + } + + return response; + } + + async signup(userData) { + const response = await this.request('/auth/signup/', { + method: 'POST', + body: JSON.stringify(userData) + }); + + if (response.token) { + this.setToken(response.token); + } + + return response; + } + + async logout() { + try { + await this.request('/auth/logout/', { method: 'POST' }); + } finally { + this.clearToken(); + } + } + + async getCurrentUser() { + return this.request('/auth/current-user/'); + } + + // Parks methods + async getParks(params = {}) { + const queryString = new URLSearchParams(params).toString(); + return this.request(`/parks/${queryString ? '?' + queryString : ''}`); + } + + async getPark(id) { + return this.request(`/parks/${id}/`); + } + + async createPark(parkData) { + return this.request('/parks/', { + method: 'POST', + body: JSON.stringify(parkData) + }); + } + + // Rides methods + async getRides(params = {}) { + const queryString = new URLSearchParams(params).toString(); + return this.request(`/rides/${queryString ? '?' + queryString : ''}`); + } + + async getRide(id) { + return this.request(`/rides/${id}/`); + } + + async createRide(rideData) { + return this.request('/rides/', { + method: 'POST', + body: JSON.stringify(rideData) + }); + } +} + +// Usage example +const api = new ThrillWikiAPI(); + +// Login +api.login('username', 'password') + .then(response => { + console.log('Logged in:', response.user); + return api.getParks(); + }) + .then(parks => { + console.log('Parks:', parks); + }) + .catch(error => { + console.error('Error:', error); + }); +``` + +### React with Axios + +```jsx +import axios from 'axios'; +import { createContext, useContext, useState, useEffect } from 'react'; + +// API Configuration +const API_BASE_URL = 'http://localhost:8000/api/v1'; + +const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// Request interceptor to add auth token +api.interceptors.request.use((config) => { + const token = localStorage.getItem('thrillwiki_token'); + if (token) { + config.headers.Authorization = `Token ${token}`; + } + return config; +}); + +// Response interceptor for error handling +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('thrillwiki_token'); + window.location.href = '/login'; + } + return Promise.reject(error); + } +); + +// Auth Context +const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const token = localStorage.getItem('thrillwiki_token'); + if (token) { + getCurrentUser(); + } else { + setLoading(false); + } + }, []); + + const getCurrentUser = async () => { + try { + const response = await api.get('/auth/current-user/'); + setUser(response.data); + } catch (error) { + localStorage.removeItem('thrillwiki_token'); + } finally { + setLoading(false); + } + }; + + const login = async (username, password) => { + const response = await api.post('/auth/login/', { username, password }); + localStorage.setItem('thrillwiki_token', response.data.token); + setUser(response.data.user); + return response.data; + }; + + const signup = async (userData) => { + const response = await api.post('/auth/signup/', userData); + localStorage.setItem('thrillwiki_token', response.data.token); + setUser(response.data.user); + return response.data; + }; + + const logout = async () => { + try { + await api.post('/auth/logout/'); + } finally { + localStorage.removeItem('thrillwiki_token'); + setUser(null); + } + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); + +// API Service +export const apiService = { + // Parks + getParks: (params) => api.get('/parks/', { params }), + getPark: (id) => api.get(`/parks/${id}/`), + createPark: (data) => api.post('/parks/', data), + updatePark: (id, data) => api.patch(`/parks/${id}/`, data), + deletePark: (id) => api.delete(`/parks/${id}/`), + + // Rides + getRides: (params) => api.get('/rides/', { params }), + getRide: (id) => api.get(`/rides/${id}/`), + createRide: (data) => api.post('/rides/', data), + updateRide: (id, data) => api.patch(`/rides/${id}/`, data), + deleteRide: (id) => api.delete(`/rides/${id}/`), +}; + +// Component Example +const ParksList = () => { + const [parks, setParks] = useState([]); + const [loading, setLoading] = useState(true); + const { user } = useAuth(); + + useEffect(() => { + const fetchParks = async () => { + try { + const response = await apiService.getParks(); + setParks(response.data.results || response.data); + } catch (error) { + console.error('Error fetching parks:', error); + } finally { + setLoading(false); + } + }; + + fetchParks(); + }, []); + + if (loading) return
Loading parks...
; + + return ( +
+

Theme Parks

+ {parks.map(park => ( +
+

{park.name}

+

{park.description}

+

Location: {park.location}

+
+ ))} +
+ ); +}; +``` + +### Vue.js 3 with Composition API + +```vue + + + +``` + +**Vue Composable (`composables/useApi.js`):** + +```javascript +import axios from 'axios' +import { ref, computed } from 'vue' + +const API_BASE_URL = 'http://localhost:8000/api/v1' + +const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// Global state +const user = ref(null) +const token = ref(localStorage.getItem('thrillwiki_token')) + +// Request interceptor +api.interceptors.request.use((config) => { + if (token.value) { + config.headers.Authorization = `Token ${token.value}` + } + return config +}) + +// Response interceptor +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + token.value = null + user.value = null + localStorage.removeItem('thrillwiki_token') + } + return Promise.reject(error) + } +) + +export function useApi() { + const isAuthenticated = computed(() => !!token.value) + + const setToken = (newToken) => { + token.value = newToken + localStorage.setItem('thrillwiki_token', newToken) + } + + const clearToken = () => { + token.value = null + user.value = null + localStorage.removeItem('thrillwiki_token') + } + + const login = async (username, password) => { + const response = await api.post('/auth/login/', { username, password }) + setToken(response.data.token) + user.value = response.data.user + return response.data + } + + const signup = async (userData) => { + const response = await api.post('/auth/signup/', userData) + setToken(response.data.token) + user.value = response.data.user + return response.data + } + + const logout = async () => { + try { + await api.post('/auth/logout/') + } finally { + clearToken() + } + } + + const getCurrentUser = async () => { + const response = await api.get('/auth/current-user/') + user.value = response.data + return response.data + } + + const apiService = { + // Parks + getParks: (params) => api.get('/parks/', { params }), + getPark: (id) => api.get(`/parks/${id}/`), + createPark: (data) => api.post('/parks/', data), + updatePark: (id, data) => api.patch(`/parks/${id}/`, data), + deletePark: (id) => api.delete(`/parks/${id}/`), + + // Rides + getRides: (params) => api.get('/rides/', { params }), + getRide: (id) => api.get(`/rides/${id}/`), + createRide: (data) => api.post('/rides/', data), + updateRide: (id, data) => api.patch(`/rides/${id}/`, data), + deleteRide: (id) => api.delete(`/rides/${id}/`), + } + + return { + user: readonly(user), + isAuthenticated, + login, + signup, + logout, + getCurrentUser, + apiService + } +} +``` + +### Angular with HttpClient + +```typescript +// api.service.ts +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +export interface User { + id: number; + username: string; + email: string; + first_name: string; + last_name: string; + is_active: boolean; + date_joined: string; +} + +export interface LoginResponse { + token: string; + user: User; + message: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class ApiService { + private baseURL = 'http://localhost:8000/api/v1'; + private tokenSubject = new BehaviorSubject( + localStorage.getItem('thrillwiki_token') + ); + private userSubject = new BehaviorSubject(null); + + public token$ = this.tokenSubject.asObservable(); + public user$ = this.userSubject.asObservable(); + + constructor(private http: HttpClient) { + // Initialize user if token exists + if (this.tokenSubject.value) { + this.getCurrentUser().subscribe(); + } + } + + private getHeaders(): HttpHeaders { + const token = this.tokenSubject.value; + let headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + + if (token) { + headers = headers.set('Authorization', `Token ${token}`); + } + + return headers; + } + + // Authentication methods + login(username: string, password: string): Observable { + return this.http.post(`${this.baseURL}/auth/login/`, { + username, + password + }).pipe( + tap(response => { + localStorage.setItem('thrillwiki_token', response.token); + this.tokenSubject.next(response.token); + this.userSubject.next(response.user); + }) + ); + } + + signup(userData: any): Observable { + return this.http.post(`${this.baseURL}/auth/signup/`, userData) + .pipe( + tap(response => { + localStorage.setItem('thrillwiki_token', response.token); + this.tokenSubject.next(response.token); + this.userSubject.next(response.user); + }) + ); + } + + logout(): Observable { + return this.http.post(`${this.baseURL}/auth/logout/`, {}, { + headers: this.getHeaders() + }).pipe( + tap(() => { + localStorage.removeItem('thrillwiki_token'); + this.tokenSubject.next(null); + this.userSubject.next(null); + }) + ); + } + + getCurrentUser(): Observable { + return this.http.get(`${this.baseURL}/auth/current-user/`, { + headers: this.getHeaders() + }).pipe( + tap(user => this.userSubject.next(user)) + ); + } + + // Parks methods + getParks(params?: any): Observable { + let httpParams = new HttpParams(); + if (params) { + Object.keys(params).forEach(key => { + httpParams = httpParams.set(key, params[key]); + }); + } + + return this.http.get(`${this.baseURL}/parks/`, { + headers: this.getHeaders(), + params: httpParams + }); + } + + getPark(id: number): Observable { + return this.http.get(`${this.baseURL}/parks/${id}/`, { + headers: this.getHeaders() + }); + } + + createPark(parkData: any): Observable { + return this.http.post(`${this.baseURL}/parks/`, parkData, { + headers: this.getHeaders() + }); + } + + // Rides methods + getRides(params?: any): Observable { + let httpParams = new HttpParams(); + if (params) { + Object.keys(params).forEach(key => { + httpParams = httpParams.set(key, params[key]); + }); + } + + return this.http.get(`${this.baseURL}/rides/`, { + headers: this.getHeaders(), + params: httpParams + }); + } + + getRide(id: number): Observable { + return this.http.get(`${this.baseURL}/rides/${id}/`, { + headers: this.getHeaders() + }); + } + + createRide(rideData: any): Observable { + return this.http.post(`${this.baseURL}/rides/`, rideData, { + headers: this.getHeaders() + }); + } +} +``` + +**Component Example:** + +```typescript +// parks-list.component.ts +import { Component, OnInit } from '@angular/core'; +import { ApiService } from '../services/api.service'; + +@Component({ + selector: 'app-parks-list', + template: ` +
+

Theme Parks

+
Loading parks...
+
+
+

{{ park.name }}

+

{{ park.description }}

+

Location: {{ park.location }}

+
+
+
+ ` +}) +export class ParksListComponent implements OnInit { + parks: any[] = []; + loading = true; + + constructor(private apiService: ApiService) {} + + ngOnInit() { + this.apiService.getParks().subscribe({ + next: (response) => { + this.parks = response.results || response; + this.loading = false; + }, + error: (error) => { + console.error('Error fetching parks:', error); + this.loading = false; + } + }); + } +} +``` + +## Dark/Light Mode Requirements + +ThrillWiki requires comprehensive dark/light mode support across all frontend implementations. + +### CSS Variables Approach + +```css +:root { + /* Light mode colors */ + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --text-primary: #212529; + --text-secondary: #6c757d; + --border-color: #dee2e6; + --accent-color: #007bff; +} + +[data-theme="dark"] { + /* Dark mode colors */ + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --text-primary: #ffffff; + --text-secondary: #b0b0b0; + --border-color: #404040; + --accent-color: #4dabf7; +} + +body { + background-color: var(--bg-primary); + color: var(--text-primary); + transition: background-color 0.3s ease, color 0.3s ease; +} +``` + +### Theme Toggle Implementation + +**Vanilla JavaScript:** +```javascript +class ThemeManager { + constructor() { + this.theme = localStorage.getItem('theme') || 'light'; + this.applyTheme(); + } + + applyTheme() { + document.documentElement.setAttribute('data-theme', this.theme); + localStorage.setItem('theme', this.theme); + } + + toggle() { + this.theme = this.theme === 'light' ? 'dark' : 'light'; + this.applyTheme(); + } + + setTheme(theme) { + this.theme = theme; + this.applyTheme(); + } +} + +const themeManager = new ThemeManager(); + +// Theme toggle button +document.getElementById('theme-toggle').addEventListener('click', () => { + themeManager.toggle(); +}); +``` + +**React Hook:** +```jsx +import { useState, useEffect } from 'react'; + +export const useTheme = () => { + const [theme, setTheme] = useState(() => { + return localStorage.getItem('theme') || 'light'; + }); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme(prev => prev === 'light' ? 'dark' : 'light'); + }; + + return { theme, toggleTheme, setTheme }; +}; + +// Component usage +const ThemeToggle = () => { + const { theme, toggleTheme } = useTheme(); + + return ( + + ); +}; +``` + +**Vue Composable:** +```javascript +import { ref, watch } from 'vue'; + +export function useTheme() { + const theme = ref(localStorage.getItem('theme') || 'light'); + + const applyTheme = (newTheme) => { + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + }; + + const toggleTheme = () => { + theme.value = theme.value === 'light' ? 'dark' : 'light'; + }; + + const setTheme = (newTheme) => { + theme.value = newTheme; + }; + + watch(theme, applyTheme, { immediate: true }); + + return { + theme: readonly(theme), + toggleTheme, + setTheme + }; +} +``` + +### Tailwind CSS Integration + +If using Tailwind CSS, configure dark mode in `tailwind.config.js`: + +```javascript +module.exports = { + darkMode: ['class', '[data-theme="dark"]'], + theme: { + extend: { + colors: { + primary: { + light: '#ffffff', + dark: '#1a1a1a' + }, + secondary: { + light: '#f8f9fa', + dark: '#2d2d2d' + } + } + } + } +} +``` + +Usage in components: +```html +
+ Content that adapts to theme +
+``` + +## Error Handling + +### HTTP Status Codes + +The API returns standard HTTP status codes: + +- **200 OK** - Successful GET, PUT, PATCH +- **201 Created** - Successful POST +- **204 No Content** - Successful DELETE +- **400 Bad Request** - Invalid request data +- **401 Unauthorized** - Authentication required +- **403 Forbidden** - Permission denied +- **404 Not Found** - Resource not found +- **500 Internal Server Error** - Server error + +### Error Response Format + +```json +{ + "error": "Error message", + "details": { + "field_name": ["Field-specific error message"] + } +} +``` + +### Error Handling Examples + +**JavaScript:** +```javascript +async function handleApiCall(apiFunction) { + try { + const response = await apiFunction(); + return response; + } catch (error) { + if (error.response) { + // Server responded with error status + const { status, data } = error.response; + + switch (status) { + case 401: + // Handle authentication error + redirectToLogin(); + break; + case 403: + // Handle permission error + showErrorMessage('You do not have permission to perform this action'); + break; + case 404: + // Handle not found + showErrorMessage('Resource not found'); + break; + case 500: + // Handle server error + showErrorMessage('Server error. Please try again later.'); + break; + default: + // Handle other errors + showErrorMessage(data.error || 'An error occurred'); + } + } else if (error.request) { + // Network error + showErrorMessage('Network error. Please check your connection.'); + } else { + // Other error + showErrorMessage('An unexpected error occurred'); + } + + throw error; + } +} +``` + +**React Error Boundary:** +```jsx +class ApiErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error('API Error:', error, errorInfo); + } + + render() { + if + if (this.state.hasError) { + return ( +
+

Something went wrong

+

Please try refreshing the page or contact support if the problem persists.

+
+ ); + } + + return this.props.children; + } +} +``` + +## Best Practices + +### Security Considerations + +1. **Token Storage** + - Use `httpOnly` cookies for production when possible + - Avoid storing tokens in localStorage for sensitive applications + - Implement token refresh mechanisms + +2. **HTTPS Only** + - Always use HTTPS in production + - Configure secure cookie flags + +3. **Input Validation** + - Validate all user inputs on the frontend + - Sanitize data before sending to API + +4. **CORS Configuration** + - Configure CORS properly in Django settings + - Restrict allowed origins in production + +### Performance Optimization + +1. **Request Caching** + ```javascript + // Simple cache implementation + class ApiCache { + constructor(ttl = 300000) { // 5 minutes default + this.cache = new Map(); + this.ttl = ttl; + } + + get(key) { + const item = this.cache.get(key); + if (!item) return null; + + if (Date.now() > item.expiry) { + this.cache.delete(key); + return null; + } + + return item.data; + } + + set(key, data) { + this.cache.set(key, { + data, + expiry: Date.now() + this.ttl + }); + } + + clear() { + this.cache.clear(); + } + } + + const apiCache = new ApiCache(); + + // Usage in API calls + async function getCachedParks() { + const cacheKey = 'parks_list'; + let parks = apiCache.get(cacheKey); + + if (!parks) { + const response = await api.get('/parks/'); + parks = response.data; + apiCache.set(cacheKey, parks); + } + + return parks; + } + ``` + +2. **Request Debouncing** + ```javascript + function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } + + // Usage for search + const debouncedSearch = debounce(async (query) => { + if (query.length > 2) { + const results = await api.get('/parks/search/', { params: { q: query } }); + setSearchResults(results.data); + } + }, 300); + ``` + +3. **Pagination Handling** + ```javascript + class PaginatedAPI { + constructor(baseURL) { + this.baseURL = baseURL; + } + + async getAllPages(endpoint, params = {}) { + let allResults = []; + let nextUrl = `${this.baseURL}${endpoint}`; + + while (nextUrl) { + const response = await fetch(nextUrl, { + headers: this.getHeaders(), + ...params + }); + const data = await response.json(); + + allResults = allResults.concat(data.results || []); + nextUrl = data.next; + } + + return allResults; + } + + async getPage(endpoint, page = 1, pageSize = 20) { + const response = await fetch(`${this.baseURL}${endpoint}?page=${page}&page_size=${pageSize}`, { + headers: this.getHeaders() + }); + return response.json(); + } + } + ``` + +### Code Organization + +1. **Service Layer Pattern** + ```javascript + // services/api.js + export class APIService { + constructor(baseURL, authService) { + this.baseURL = baseURL; + this.authService = authService; + } + + async request(endpoint, options = {}) { + // Common request logic + } + } + + // services/parksService.js + export class ParksService extends APIService { + async getParks(filters = {}) { + return this.request('/parks/', { params: filters }); + } + + async getPark(id) { + return this.request(`/parks/${id}/`); + } + } + + // services/index.js + export { APIService } from './api'; + export { ParksService } from './parksService'; + export { RidesService } from './ridesService'; + export { AuthService } from './authService'; + ``` + +2. **Environment Configuration** + ```javascript + // config/api.js + const config = { + development: { + API_BASE_URL: 'http://localhost:8000/api/v1', + ENABLE_LOGGING: true, + CACHE_TTL: 60000 // 1 minute + }, + production: { + API_BASE_URL: 'https://api.thrillwiki.com/api/v1', + ENABLE_LOGGING: false, + CACHE_TTL: 300000 // 5 minutes + } + }; + + export default config[process.env.NODE_ENV || 'development']; + ``` + +## Troubleshooting + +### Common Issues + +#### 1. CORS Errors +**Problem:** Browser blocks requests due to CORS policy + +**Solution:** +- Ensure Django CORS settings are configured: + ```python + # settings.py + CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "http://127.0.0.1:3000", + "http://localhost:5173", # Vite default + ] + + CORS_ALLOW_CREDENTIALS = True + ``` + +#### 2. Authentication Token Issues +**Problem:** 401 Unauthorized errors + +**Solutions:** +- Check token format: `Authorization: Token abc123def456` +- Verify token is not expired +- Ensure token is included in requests: + ```javascript + // Check if token exists + const token = localStorage.getItem('thrillwiki_token'); + if (!token) { + // Redirect to login + window.location.href = '/login'; + return; + } + ``` + +#### 3. Network Connectivity +**Problem:** Network errors or timeouts + +**Solutions:** +- Implement retry logic: + ```javascript + async function retryRequest(fn, retries = 3, delay = 1000) { + try { + return await fn(); + } catch (error) { + if (retries > 0 && error.code === 'NETWORK_ERROR') { + await new Promise(resolve => setTimeout(resolve, delay)); + return retryRequest(fn, retries - 1, delay * 2); + } + throw error; + } + } + ``` + +#### 4. Data Serialization Issues +**Problem:** Unexpected data formats or missing fields + +**Solutions:** +- Always validate API responses: + ```javascript + function validateParkData(park) { + const required = ['id', 'name', 'location']; + const missing = required.filter(field => !park[field]); + + if (missing.length > 0) { + throw new Error(`Missing required fields: ${missing.join(', ')}`); + } + + return park; + } + ``` + +#### 5. Proxy Configuration Issues (Vite/Webpack) +**Problem:** API calls fail in development due to proxy misconfiguration + +**Vite Solution:** +```javascript +// vite.config.js +export default defineConfig({ + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + secure: false, + } + } + } +}); +``` + +**Webpack Solution:** +```javascript +// webpack.config.js or next.config.js +module.exports = { + async rewrites() { + return [ + { + source: '/api/:path*', + destination: 'http://localhost:8000/api/:path*', + }, + ]; + }, +}; +``` + +### Debugging Tools + +#### 1. API Request Logging +```javascript +class APILogger { + static log(method, url, data, response) { + if (process.env.NODE_ENV === 'development') { + console.group(`🌐 API ${method.toUpperCase()} ${url}`); + if (data) console.log('πŸ“€ Request:', data); + console.log('πŸ“₯ Response:', response); + console.groupEnd(); + } + } + + static error(method, url, error) { + console.group(`❌ API ${method.toUpperCase()} ${url} FAILED`); + console.error('Error:', error); + console.groupEnd(); + } +} + +// Usage in API service +async request(endpoint, options = {}) { + try { + const response = await fetch(url, options); + const data = await response.json(); + + APILogger.log(options.method || 'GET', endpoint, options.body, data); + return data; + } catch (error) { + APILogger.error(options.method || 'GET', endpoint, error); + throw error; + } +} +``` + +#### 2. Network Monitoring +```javascript +// Monitor network status +class NetworkMonitor { + constructor() { + this.isOnline = navigator.onLine; + this.listeners = []; + + window.addEventListener('online', () => this.setOnline(true)); + window.addEventListener('offline', () => this.setOnline(false)); + } + + setOnline(status) { + this.isOnline = status; + this.listeners.forEach(listener => listener(status)); + } + + onStatusChange(callback) { + this.listeners.push(callback); + } +} + +const networkMonitor = new NetworkMonitor(); +networkMonitor.onStatusChange((isOnline) => { + if (isOnline) { + console.log('🟒 Back online - retrying failed requests'); + // Retry failed requests + } else { + console.log('πŸ”΄ Gone offline - queuing requests'); + // Queue requests for later + } +}); +``` + +### Testing API Integration + +#### Unit Testing Example (Jest) +```javascript +// __tests__/api.test.js +import { APIService } from '../services/api'; + +// Mock fetch +global.fetch = jest.fn(); + +describe('APIService', () => { + let apiService; + + beforeEach(() => { + apiService = new APIService('http://localhost:8000/api/v1'); + fetch.mockClear(); + }); + + test('should fetch parks successfully', async () => { + const mockParks = [ + { id: 1, name: 'Test Park', location: 'Test Location' } + ]; + + fetch.mockResolvedValueOnce({ + ok: true, + json: async () => ({ results: mockParks }) + }); + + const parks = await apiService.getParks(); + + expect(fetch).toHaveBeenCalledWith( + 'http://localhost:8000/api/v1/parks/', + expect.objectContaining({ + headers: expect.objectContaining({ + 'Content-Type': 'application/json' + }) + }) + ); + + expect(parks.results).toEqual(mockParks); + }); + + test('should handle authentication errors', async () => { + fetch.mockResolvedValueOnce({ + ok: false, + status: 401, + json: async () => ({ error: 'Unauthorized' }) + }); + + await expect(apiService.getParks()).rejects.toThrow('Unauthorized'); + }); +}); +``` + +### Performance Monitoring + +```javascript +// Performance monitoring utility +class PerformanceMonitor { + static startTimer(label) { + performance.mark(`${label}-start`); + } + + static endTimer(label) { + performance.mark(`${label}-end`); + performance.measure(label, `${label}-start`, `${label}-end`); + + const measure = performance.getEntriesByName(label)[0]; + console.log(`⏱️ ${label}: ${measure.duration.toFixed(2)}ms`); + + // Clean up + performance.clearMarks(`${label}-start`); + performance.clearMarks(`${label}-end`); + performance.clearMeasures(label); + } +} + +// Usage +PerformanceMonitor.startTimer('api-parks-fetch'); +const parks = await api.getParks(); +PerformanceMonitor.endTimer('api-parks-fetch'); +``` + +## Additional Resources + +### API Documentation +- **Swagger/OpenAPI Docs**: `http://localhost:8000/api/docs/` +- **ReDoc Documentation**: `http://localhost:8000/api/redoc/` +- **API Schema**: `http://localhost:8000/api/schema/` + +### Development Tools +- **Django Admin**: `http://localhost:8000/admin/` +- **Health Check**: `http://localhost:8000/api/v1/health/` +- **Environment Settings**: `http://localhost:8000/env-settings/` + +### Example Projects +Check the `frontend/` directory for a complete Vue.js implementation example that demonstrates all the patterns described in this guide. + +### Community Resources +- **Django REST Framework Documentation**: https://www.django-rest-framework.org/ +- **Vue.js Documentation**: https://vuejs.org/ +- **React Documentation**: https://react.dev/ +- **Angular Documentation**: https://angular.io/ + +--- + +This guide provides a comprehensive foundation for connecting any frontend framework to the ThrillWiki Django backend. For specific implementation questions or issues not covered here, please refer to the project's issue tracker or documentation. \ No newline at end of file diff --git a/frontend/.editorconfig b/frontend/.editorconfig deleted file mode 100644 index 3b510aa6..00000000 --- a/frontend/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] -charset = utf-8 -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true -end_of_line = lf -max_line_length = 100 diff --git a/frontend/.env.development b/frontend/.env.development deleted file mode 100644 index d7c34718..00000000 --- a/frontend/.env.development +++ /dev/null @@ -1,6 +0,0 @@ -# Development environment configuration -VITE_API_BASE_URL= -VITE_APP_ENV=development -VITE_APP_NAME=ThrillWiki -VITE_APP_VERSION=1.0.0 -VITE_DEBUG=true \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production deleted file mode 100644 index c0614736..00000000 --- a/frontend/.env.production +++ /dev/null @@ -1,6 +0,0 @@ -# Production environment configuration -VITE_API_BASE_URL=https://api.thrillwiki.com -VITE_APP_ENV=production -VITE_APP_NAME=ThrillWiki -VITE_APP_VERSION=1.0.0 -VITE_DEBUG=false \ No newline at end of file diff --git a/frontend/.env.staging b/frontend/.env.staging deleted file mode 100644 index d47546ff..00000000 --- a/frontend/.env.staging +++ /dev/null @@ -1,6 +0,0 @@ -# Staging environment configuration -VITE_API_BASE_URL=https://staging-api.thrillwiki.com -VITE_APP_ENV=staging -VITE_APP_NAME=ThrillWiki (Staging) -VITE_APP_VERSION=1.0.0 -VITE_DEBUG=true \ No newline at end of file diff --git a/frontend/.gitattributes b/frontend/.gitattributes deleted file mode 100644 index 5fc40a09..00000000 --- a/frontend/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* text=auto eol=lf -# SCM syntax highlighting & preventing 3-way merges -pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index ad465589..00000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -.DS_Store -dist -dist-ssr -coverage -*.local - -/cypress/videos/ -/cypress/screenshots/ - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -*.tsbuildinfo - -test-results/ -playwright-report/ -# pixi environments -.pixi/* -!.pixi/config.toml diff --git a/frontend/.nvmrc b/frontend/.nvmrc deleted file mode 100644 index b009dfb9..00000000 --- a/frontend/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/* diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json deleted file mode 100644 index 29a2402e..00000000 --- a/frontend/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/prettierrc", - "semi": false, - "singleQuote": true, - "printWidth": 100 -} diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 299edea1..00000000 --- a/frontend/README.md +++ /dev/null @@ -1,384 +0,0 @@ -# ThrillWiki Frontend - -Modern Vue.js 3 SPA frontend for the ThrillWiki theme park and roller coaster information system. - -## πŸ—οΈ Architecture Overview - -This frontend is built with Vue 3 and follows modern development practices: - -``` -frontend/ -β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ components/ # Reusable UI components -β”‚ β”‚ β”œβ”€β”€ ui/ # Base UI components (shadcn-vue style) -β”‚ β”‚ β”œβ”€β”€ layout/ # Layout components (Navbar, ThemeController) -β”‚ β”‚ β”œβ”€β”€ button/ # Button variants -β”‚ β”‚ β”œβ”€β”€ icon/ # Icon components -β”‚ β”‚ └── state-layer/ # Material Design state layers -β”‚ β”œβ”€β”€ views/ # Page components -β”‚ β”‚ β”œβ”€β”€ Home.vue # Landing page -β”‚ β”‚ β”œβ”€β”€ SearchResults.vue # Search results page -β”‚ β”‚ β”œβ”€β”€ parks/ # Park-related pages -β”‚ β”‚ └── rides/ # Ride-related pages -β”‚ β”œβ”€β”€ stores/ # Pinia state management -β”‚ β”œβ”€β”€ router/ # Vue Router configuration -β”‚ β”œβ”€β”€ services/ # API services and utilities -β”‚ β”œβ”€β”€ types/ # TypeScript type definitions -β”‚ β”œβ”€β”€ App.vue # Root component -β”‚ └── main.ts # Application entry point -β”œβ”€β”€ public/ # Static assets -β”œβ”€β”€ dist/ # Production build output -└── e2e/ # End-to-end tests -``` - -## πŸš€ Technology Stack - -### Core Framework -- **Vue 3** with Composition API and ` - - diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 2c5265fe..00000000 --- a/frontend/package.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.0", - "private": true, - "type": "module", - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "scripts": { - "dev": "vite", - "build": "run-p type-check \"build-only {@}\" --", - "preview": "vite preview", - "test:unit": "vitest", - "test:e2e": "playwright test", - "build-only": "vite build", - "type-check": "vue-tsc --build", - "lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore", - "lint:eslint": "eslint . --fix", - "lint": "run-s lint:*", - "format": "prettier --write src/", - "add": "liftkit add" - }, - "dependencies": { - "@csstools/normalize.css": "^12.1.1", - "@material/material-color-utilities": "^0.3.0", - "@primeuix/themes": "^1.2.3", - "@primevue/forms": "^4.3.7", - "@primevue/themes": "^4.3.7", - "@vueuse/core": "^13.8.0", - "lodash-es": "^4.17.21", - "pinia": "^3.0.3", - "primeicons": "^7.0.0", - "primevue": "^4.3.7", - "tw-animate-css": "^1.3.7", - "vue": "^3.5.20", - "vue-router": "^4.5.1" - }, - "devDependencies": { - "@chainlift/liftkit": "^0.2.0", - "@playwright/test": "^1.55.0", - "@prettier/plugin-oxc": "^0.0.4", - "@primevue/auto-import-resolver": "^4.3.7", - "@tailwindcss/postcss": "^4.1.12", - "@tailwindcss/vite": "^4.1.12", - "@tsconfig/node22": "^22.0.2", - "@types/jsdom": "^21.1.7", - "@types/node": "^24.3.0", - "@vitejs/plugin-vue": "^6.0.1", - "@vitest/eslint-plugin": "^1.3.4", - "@vue/eslint-config-prettier": "^10.2.0", - "@vue/eslint-config-typescript": "^14.6.0", - "@vue/test-utils": "^2.4.6", - "@vue/tsconfig": "^0.8.1", - "autoprefixer": "^10.4.21", - "eslint": "^9.34.0", - "eslint-plugin-oxlint": "~1.13.0", - "eslint-plugin-playwright": "^2.2.2", - "eslint-plugin-vue": "~10.4.0", - "jiti": "^2.5.1", - "jsdom": "^26.1.0", - "npm-run-all2": "^8.0.4", - "oxlint": "~1.13.0", - "postcss": "^8.5.6", - "prettier": "3.6.2", - "tailwindcss": "^4.1.12", - "typescript": "~5.9.2", - "unplugin-vue-components": "^29.0.0", - "vite": "^7.1.3", - "vite-plugin-vue-devtools": "^8.0.1", - "vitest": "^3.2.4", - "vue-tsc": "^3.0.6" - }, - "trustedDependencies": [ - "@tailwindcss/oxide" - ] -} \ No newline at end of file diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts deleted file mode 100644 index 5ece9567..00000000 --- a/frontend/playwright.config.ts +++ /dev/null @@ -1,110 +0,0 @@ -import process from 'node:process' -import { defineConfig, devices } from '@playwright/test' - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: './e2e', - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - - /* Only on CI systems run the tests headless */ - headless: !!process.env.CI, - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - webServer: { - /** - * Use the dev server by default for faster feedback loop. - * Use the preview server on CI for more realistic testing. - * Playwright will re-use the local server if there is already a dev-server running. - */ - command: process.env.CI ? 'npm run preview' : 'npm run dev', - port: process.env.CI ? 4173 : 5173, - reuseExistingServer: !process.env.CI, - }, -}) diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml deleted file mode 100644 index d91021c8..00000000 --- a/frontend/pnpm-lock.yaml +++ /dev/null @@ -1,5520 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@csstools/normalize.css': - specifier: ^12.1.1 - version: 12.1.1 - '@heroicons/vue': - specifier: ^2.2.0 - version: 2.2.0(vue@3.5.20(typescript@5.9.2)) - '@material/material-color-utilities': - specifier: ^0.3.0 - version: 0.3.0 - '@primeuix/themes': - specifier: ^1.2.3 - version: 1.2.3 - '@primevue/forms': - specifier: ^4.3.7 - version: 4.3.7(vue@3.5.20(typescript@5.9.2)) - '@primevue/themes': - specifier: ^4.3.7 - version: 4.3.7 - '@vueuse/core': - specifier: ^13.8.0 - version: 13.8.0(vue@3.5.20(typescript@5.9.2)) - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - pinia: - specifier: ^3.0.3 - version: 3.0.3(typescript@5.9.2)(vue@3.5.20(typescript@5.9.2)) - primeicons: - specifier: ^7.0.0 - version: 7.0.0 - primevue: - specifier: ^4.3.7 - version: 4.3.7(vue@3.5.20(typescript@5.9.2)) - tw-animate-css: - specifier: ^1.3.7 - version: 1.3.7 - vue: - specifier: ^3.5.20 - version: 3.5.20(typescript@5.9.2) - vue-router: - specifier: ^4.5.1 - version: 4.5.1(vue@3.5.20(typescript@5.9.2)) - devDependencies: - '@chainlift/liftkit': - specifier: ^0.2.0 - version: 0.2.0 - '@playwright/test': - specifier: ^1.55.0 - version: 1.55.0 - '@prettier/plugin-oxc': - specifier: ^0.0.4 - version: 0.0.4 - '@primevue/auto-import-resolver': - specifier: ^4.3.7 - version: 4.3.7 - '@tailwindcss/postcss': - specifier: ^4.1.12 - version: 4.1.12 - '@tailwindcss/vite': - specifier: ^4.1.12 - version: 4.1.12(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - '@tsconfig/node22': - specifier: ^22.0.2 - version: 22.0.2 - '@types/jsdom': - specifier: ^21.1.7 - version: 21.1.7 - '@types/node': - specifier: ^24.3.0 - version: 24.3.0 - '@vitejs/plugin-vue': - specifier: ^6.0.1 - version: 6.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2)) - '@vitest/eslint-plugin': - specifier: ^1.3.4 - version: 1.3.4(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)(vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(stylus@0.57.0)) - '@vue/eslint-config-prettier': - specifier: ^10.2.0 - version: 10.2.0(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2) - '@vue/eslint-config-typescript': - specifier: ^14.6.0 - version: 14.6.0(eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@vue/test-utils': - specifier: ^2.4.6 - version: 2.4.6 - '@vue/tsconfig': - specifier: ^0.8.1 - version: 0.8.1(typescript@5.9.2)(vue@3.5.20(typescript@5.9.2)) - autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) - eslint: - specifier: ^9.34.0 - version: 9.34.0(jiti@2.5.1) - eslint-plugin-oxlint: - specifier: ~1.13.0 - version: 1.13.0 - eslint-plugin-playwright: - specifier: ^2.2.2 - version: 2.2.2(eslint@9.34.0(jiti@2.5.1)) - eslint-plugin-vue: - specifier: ~10.4.0 - version: 10.4.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))) - jiti: - specifier: ^2.5.1 - version: 2.5.1 - jsdom: - specifier: ^26.1.0 - version: 26.1.0 - npm-run-all2: - specifier: ^8.0.4 - version: 8.0.4 - oxlint: - specifier: ~1.13.0 - version: 1.13.0(oxlint-tsgolint@0.0.4) - postcss: - specifier: ^8.5.6 - version: 8.5.6 - prettier: - specifier: 3.6.2 - version: 3.6.2 - tailwindcss: - specifier: ^4.1.12 - version: 4.1.12 - typescript: - specifier: ~5.9.2 - version: 5.9.2 - unplugin-vue-components: - specifier: ^29.0.0 - version: 29.0.0(@babel/parser@7.28.3)(vue@3.5.20(typescript@5.9.2)) - vite: - specifier: ^7.1.3 - version: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vite-plugin-vue-devtools: - specifier: ^8.0.1 - version: 8.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2)) - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(stylus@0.57.0) - vue-tsc: - specifier: ^3.0.6 - version: 3.0.6(typescript@5.9.2) - -packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.3': - resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.28.3': - resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.27.1': - resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.3': - resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-proposal-decorators@7.28.0': - resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.28.0': - resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.3': - resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} - engines: {node: '>=6.9.0'} - - '@chainlift/liftkit@0.2.0': - resolution: {integrity: sha512-9St6tcUE46OHarYOdG2mJQIIbEL2djfKB3YtwnFWoKpC5fwFsK48D395QOxGcqnz4p1xZ3dvQ8ae/8zn+g1BdQ==} - engines: {node: '>=16'} - hasBin: true - - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} - - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - - '@csstools/normalize.css@12.1.1': - resolution: {integrity: sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==} - - '@emnapi/core@1.4.5': - resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} - - '@emnapi/runtime@1.4.5': - resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} - - '@emnapi/wasi-threads@1.0.4': - resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} - - '@esbuild/aix-ppc64@0.25.9': - resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.9': - resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.9': - resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.9': - resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.9': - resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.9': - resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.9': - resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.9': - resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.9': - resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.9': - resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.9': - resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.9': - resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.9': - resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.9': - resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.9': - resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.9': - resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.9': - resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.9': - resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.9': - resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.9': - resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.9': - resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.9': - resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.9': - resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.9': - resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.9': - resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.9': - resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.0': - resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.3.1': - resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.15.2': - resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.34.0': - resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.3.5': - resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@heroicons/vue@2.2.0': - resolution: {integrity: sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==} - peerDependencies: - vue: '>= 3' - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - - '@material/material-color-utilities@0.3.0': - resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} - - '@napi-rs/wasm-runtime@0.2.12': - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@one-ini/wasm@0.1.1': - resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} - - '@oxc-parser/binding-android-arm64@0.74.0': - resolution: {integrity: sha512-lgq8TJq22eyfojfa2jBFy2m66ckAo7iNRYDdyn9reXYA3I6Wx7tgGWVx1JAp1lO+aUiqdqP/uPlDaETL9tqRcg==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [android] - - '@oxc-parser/binding-darwin-arm64@0.74.0': - resolution: {integrity: sha512-xbY/io/hkARggbpYEMFX6CwFzb7f4iS6WuBoBeZtdqRWfIEi7sm/uYWXfyVeB8uqOATvJ07WRFC2upI8PSI83g==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [darwin] - - '@oxc-parser/binding-darwin-x64@0.74.0': - resolution: {integrity: sha512-FIj2gAGtFaW0Zk+TnGyenMUoRu1ju+kJ/h71D77xc1owOItbFZFGa+4WSVck1H8rTtceeJlK+kux+vCjGFCl9Q==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [darwin] - - '@oxc-parser/binding-freebsd-x64@0.74.0': - resolution: {integrity: sha512-W1I+g5TJg0TRRMHgEWNWsTIfe782V3QuaPgZxnfPNmDMywYdtlzllzclBgaDq6qzvZCCQc/UhvNb37KWTCTj8A==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [freebsd] - - '@oxc-parser/binding-linux-arm-gnueabihf@0.74.0': - resolution: {integrity: sha512-gxqkyRGApeVI8dgvJ19SYe59XASW3uVxF1YUgkE7peW/XIg5QRAOVTFKyTjI9acYuK1MF6OJHqx30cmxmZLtiQ==} - engines: {node: '>=20.0.0'} - cpu: [arm] - os: [linux] - - '@oxc-parser/binding-linux-arm-musleabihf@0.74.0': - resolution: {integrity: sha512-jpnAUP4Fa93VdPPDzxxBguJmldj/Gpz7wTXKFzpAueqBMfZsy9KNC+0qT2uZ9HGUDMzNuKw0Se3bPCpL/gfD2Q==} - engines: {node: '>=20.0.0'} - cpu: [arm] - os: [linux] - - '@oxc-parser/binding-linux-arm64-gnu@0.74.0': - resolution: {integrity: sha512-fcWyM7BNfCkHqIf3kll8fJctbR/PseL4RnS2isD9Y3FFBhp4efGAzhDaxIUK5GK7kIcFh1P+puIRig8WJ6IMVQ==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [linux] - - '@oxc-parser/binding-linux-arm64-musl@0.74.0': - resolution: {integrity: sha512-AMY30z/C77HgiRRJX7YtVUaelKq1ex0aaj28XoJu4SCezdS8i0IftUNTtGS1UzGjGZB8zQz5SFwVy4dRu4GLwg==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [linux] - - '@oxc-parser/binding-linux-riscv64-gnu@0.74.0': - resolution: {integrity: sha512-/RZAP24TgZo4vV/01TBlzRqs0R7E6xvatww4LnmZEBBulQBU/SkypDywfriFqWuFoa61WFXPV7sLcTjJGjim/w==} - engines: {node: '>=20.0.0'} - cpu: [riscv64] - os: [linux] - - '@oxc-parser/binding-linux-s390x-gnu@0.74.0': - resolution: {integrity: sha512-620J1beNAlGSPBD+Msb3ptvrwxu04B8iULCH03zlf0JSLy/5sqlD6qBs0XUVkUJv1vbakUw1gfVnUQqv0UTuEg==} - engines: {node: '>=20.0.0'} - cpu: [s390x] - os: [linux] - - '@oxc-parser/binding-linux-x64-gnu@0.74.0': - resolution: {integrity: sha512-WBFgQmGtFnPNzHyLKbC1wkYGaRIBxXGofO0+hz1xrrkPgbxbJS1Ukva1EB8sPaVBBQ52Bdc2GjLSp721NWRvww==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [linux] - - '@oxc-parser/binding-linux-x64-musl@0.74.0': - resolution: {integrity: sha512-y4mapxi0RGqlp3t6Sm+knJlAEqdKDYrEue2LlXOka/F2i4sRN0XhEMPiSOB3ppHmvK4I2zY2XBYTsX1Fel0fAg==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [linux] - - '@oxc-parser/binding-wasm32-wasi@0.74.0': - resolution: {integrity: sha512-yDS9bRDh5ymobiS2xBmjlrGdUuU61IZoJBaJC5fELdYT5LJNBXlbr3Yc6m2PWfRJwkH6Aq5fRvxAZ4wCbkGa8w==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@oxc-parser/binding-win32-arm64-msvc@0.74.0': - resolution: {integrity: sha512-XFWY52Rfb4N5wEbMCTSBMxRkDLGbAI9CBSL24BIDywwDJMl31gHEVlmHdCDRoXAmanCI6gwbXYTrWe0HvXJ7Aw==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [win32] - - '@oxc-parser/binding-win32-x64-msvc@0.74.0': - resolution: {integrity: sha512-1D3x6iU2apLyfTQHygbdaNbX3nZaHu4yaXpD7ilYpoLo7f0MX0tUuoDrqJyJrVGqvyXgc0uz4yXz9tH9ZZhvvg==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [win32] - - '@oxc-project/types@0.74.0': - resolution: {integrity: sha512-KOw/RZrVlHGhCXh1RufBFF7Nuo7HdY5w1lRJukM/igIl6x9qtz8QycDvZdzb4qnHO7znrPyo2sJrFJK2eKHgfQ==} - - '@oxlint-tsgolint/darwin-arm64@0.0.4': - resolution: {integrity: sha512-qL0zqIYdYrXl6ghTIHnhJkvyYy1eKz0P8YIEp59MjY3/zNiyk/gtyp8LkwZdqb9ezbcX9UDQhSuSO1wURJsq8g==} - cpu: [arm64] - os: [darwin] - - '@oxlint-tsgolint/darwin-x64@0.0.4': - resolution: {integrity: sha512-c3nSjqmDSKzemChAEUv/zy2e9cwgkkO/7rz4Y447+8pSbeZNHi3RrNpVHdrKL/Qep4pt6nFZE+6PoczZxHNQjg==} - cpu: [x64] - os: [darwin] - - '@oxlint-tsgolint/linux-arm64@0.0.4': - resolution: {integrity: sha512-P2BA54c/Ej5AGkChH1/7zMd6PwZfa+jnw8juB/JWops+BX+lbhbbBHz0cYduDBgWYjRo4e3OVJOTskqcpuMfNw==} - cpu: [arm64] - os: [linux] - - '@oxlint-tsgolint/linux-x64@0.0.4': - resolution: {integrity: sha512-hbgLpnDNicPrbHOAQ9nNfLOSrUrdWANP/umR7P/cwCc1sv66eEs7bm4G3mrhRU8aXFBJmbhdNqiDSUkYYvHWJQ==} - cpu: [x64] - os: [linux] - - '@oxlint-tsgolint/win32-arm64@0.0.4': - resolution: {integrity: sha512-ozKEppmwZhC5LMedClBEat6cXgBGUvxGOgsKK2ZZNE6zSScX7QbvJAOt3nWMGs8GQshHy/6ndMB33+uRloglQA==} - cpu: [arm64] - os: [win32] - - '@oxlint-tsgolint/win32-x64@0.0.4': - resolution: {integrity: sha512-gLfx+qogW21QcaRKFg6ARgra7tSPqyn+Ems3FgTUyxV4OpJYn7KsQroygxOWElqv6JUobtvHBrxdB6YhlvERbQ==} - cpu: [x64] - os: [win32] - - '@oxlint/darwin-arm64@1.13.0': - resolution: {integrity: sha512-evpsj1aaWNEd2VRGTbptiMwC8vYSDadAYtq92Ks3UIe0VoMtY9n5bLeD9Ctw/OHIM7Eh7/EQlNDLOOP/b2GBKA==} - cpu: [arm64] - os: [darwin] - - '@oxlint/darwin-x64@1.13.0': - resolution: {integrity: sha512-a4gmSsuQq/ZK/QRDlAcfcwF4UVErZ3Q0noBkypyMdacizLzexlKQvWhXC5Bh1v4/9cWempx+Uf6iaScfo7FmCg==} - cpu: [x64] - os: [darwin] - - '@oxlint/linux-arm64-gnu@1.13.0': - resolution: {integrity: sha512-GT8WyPomb2AE5ciNzmDZlvVdYL2OmWObaV47dwAk4KH13IAqduOlA17S5IZRrwW1q4FHsRhfJ1eVofAhOtZexQ==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-arm64-musl@1.13.0': - resolution: {integrity: sha512-EY8PHd4U0QYoPFVkGbkBPAN1ZDXmIr5Am6QOqnPtvrOVfR6cRW/o9Qd9Q3zB+HR+pEHl8d25/QSgHpaSQr+hEA==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-x64-gnu@1.13.0': - resolution: {integrity: sha512-iP30520DYHsqAk3rmCJ4YpcNuWJejhbvl/YcHmrcWH8OJ5a+He2EG6gU9BogfFzsM1HtDn3pZbn69PItqaLJCg==} - cpu: [x64] - os: [linux] - - '@oxlint/linux-x64-musl@1.13.0': - resolution: {integrity: sha512-SJl0aenYerXS6uFshdpsracwl02sr8dpUK1522p4Tp27aXHUxk55gF5YmFj9rGUQ9h6MyZgJL9fNS5U7PUUxxA==} - cpu: [x64] - os: [linux] - - '@oxlint/win32-arm64@1.13.0': - resolution: {integrity: sha512-nAxRno4VF73obGWbBMMslWDYx0hFgqwKR7wqhhVowH5793p1tHvYbV9lrUY8lRqMUHRpYP4pahcipoAEiTlf1w==} - cpu: [arm64] - os: [win32] - - '@oxlint/win32-x64@1.13.0': - resolution: {integrity: sha512-8p6OwSl6/iauD5TZrTXXZFdKZkj1blGwMOlhnHfSb6FRcjcvR6dv54u3PYssrtqh7nvHLJI0PAwSeJVhvoxxqg==} - cpu: [x64] - os: [win32] - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@playwright/test@1.55.0': - resolution: {integrity: sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==} - engines: {node: '>=18'} - hasBin: true - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@prettier/plugin-oxc@0.0.4': - resolution: {integrity: sha512-UGXe+g/rSRbglL0FOJiar+a+nUrst7KaFmsg05wYbKiInGWP6eAj/f8A2Uobgo5KxEtb2X10zeflNH6RK2xeIQ==} - engines: {node: '>=14'} - - '@primeuix/forms@0.1.0': - resolution: {integrity: sha512-LctcQidb+B5PuvAFWH24YH/SIzmHlOabLHpaTeGY/k51iBv1WyCp+5w9JMYuMB/BplSvV0ZGySxQVkN5Azr/aQ==} - engines: {node: '>=12.11.0'} - - '@primeuix/styled@0.7.2': - resolution: {integrity: sha512-tIJ6byZezTYZ9YUICNSidQHOIQOQL3zeUgjwiX0JnBTK3+WCvy4DyCBcrJ94RtiX0WGFZSYNvaGaFkTo4jU8FQ==} - engines: {node: '>=12.11.0'} - - '@primeuix/styles@1.2.3': - resolution: {integrity: sha512-+KwmQsLTYgVAqFADmO252btz40lstPML6r4QMNjxz4gLNCKVW3kPR0/aCouQx6/21+boXG1P68tu8Zk3FAKr2w==} - - '@primeuix/themes@1.2.3': - resolution: {integrity: sha512-GLAU2h6lhgln2w10EQalUQlgwbgQ0xZoIOLMNGfIvqU4O09L282P7rwKCKQksvAGAFt1GoO/Q1NgBSxnttr7iA==} - - '@primeuix/utils@0.6.1': - resolution: {integrity: sha512-tQL/ZOPgCdD+NTimlUmhyD0ey8J1XmpZE4hDHM+/fnuBicVVmlKOd5HpS748LcOVRUKbWjmEPdHX4hi5XZoC1Q==} - engines: {node: '>=12.11.0'} - - '@primevue/auto-import-resolver@4.3.7': - resolution: {integrity: sha512-U/gJ2q1XckszMcgViYsjz6+hCdhoCi+6l9JS6E93MFfsGji1NnlwM/+7ZDElGQ0CT8YuLXEOhg+HgR1Fiznljg==} - engines: {node: '>=12.11.0'} - - '@primevue/core@4.3.7': - resolution: {integrity: sha512-rYmEZTKs/C2Re+xFluY9R02+n4TcyJshooPPOG6zc23v/bmhrfcqCiS5gNiuXF/lYoOh4y8VTcHF6eZCZW724Q==} - engines: {node: '>=12.11.0'} - peerDependencies: - vue: ^3.5.0 - - '@primevue/forms@4.3.7': - resolution: {integrity: sha512-Hk52vSDZskMqJlwBiKmfjYtUL3lvTs4C4456OV7CeI++8ah094kuwlJsOulgAeYw9P2hzCoOrnMS4SoL3F3j6w==} - engines: {node: '>=12.11.0'} - - '@primevue/icons@4.3.7': - resolution: {integrity: sha512-CKiOeiJzNSbELwbQdrgWHgmfcAmjNQXqRZtBGb8sUPrB8hjWf9lYMBAbcAuqJbyLFFcC0lFiB80CfBR07FNSHw==} - engines: {node: '>=12.11.0'} - - '@primevue/metadata@4.3.7': - resolution: {integrity: sha512-nTyedHcAuqDqO79+iiyCpFe/phJH5DZEwsx+M6jXSWohHhU8pRjiRCK1rU0exyHkUbaA/QkyUUJOVb/Gkwmh5A==} - engines: {node: '>=12.11.0'} - - '@primevue/themes@4.3.7': - resolution: {integrity: sha512-Ej/3CBGpSIJFo0BJHrPa7HjyQZ8YPxQUiOuXj1tKFJkSqFxgOk52JL6aLBGdQkSnx7TjkHuHuKuk4t6SKkD5hw==} - engines: {node: '>=12.11.0'} - - '@rolldown/pluginutils@1.0.0-beta.29': - resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} - - '@rollup/rollup-android-arm-eabi@4.49.0': - resolution: {integrity: sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.49.0': - resolution: {integrity: sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.49.0': - resolution: {integrity: sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.49.0': - resolution: {integrity: sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.49.0': - resolution: {integrity: sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.49.0': - resolution: {integrity: sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.49.0': - resolution: {integrity: sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.49.0': - resolution: {integrity: sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.49.0': - resolution: {integrity: sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.49.0': - resolution: {integrity: sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.49.0': - resolution: {integrity: sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.49.0': - resolution: {integrity: sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.49.0': - resolution: {integrity: sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.49.0': - resolution: {integrity: sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.49.0': - resolution: {integrity: sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.49.0': - resolution: {integrity: sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.49.0': - resolution: {integrity: sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.49.0': - resolution: {integrity: sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.49.0': - resolution: {integrity: sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.49.0': - resolution: {integrity: sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==} - cpu: [x64] - os: [win32] - - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - - '@tailwindcss/node@4.1.12': - resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} - - '@tailwindcss/oxide-android-arm64@4.1.12': - resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.12': - resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.12': - resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.12': - resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': - resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': - resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.12': - resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.12': - resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.12': - resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.12': - resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': - resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.12': - resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.12': - resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==} - engines: {node: '>= 10'} - - '@tailwindcss/postcss@4.1.12': - resolution: {integrity: sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==} - - '@tailwindcss/vite@4.1.12': - resolution: {integrity: sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - - '@tsconfig/node22@22.0.2': - resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==} - - '@tybys/wasm-util@0.10.0': - resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/jsdom@21.1.7': - resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@24.3.0': - resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} - - '@types/tough-cookie@4.0.5': - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - - '@types/web-bluetooth@0.0.21': - resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - - '@typescript-eslint/eslint-plugin@8.41.0': - resolution: {integrity: sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.41.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.41.0': - resolution: {integrity: sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.41.0': - resolution: {integrity: sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.41.0': - resolution: {integrity: sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.41.0': - resolution: {integrity: sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.41.0': - resolution: {integrity: sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@8.41.0': - resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.41.0': - resolution: {integrity: sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.41.0': - resolution: {integrity: sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.41.0': - resolution: {integrity: sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@vitejs/plugin-vue@6.0.1': - resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - vue: ^3.2.25 - - '@vitest/eslint-plugin@1.3.4': - resolution: {integrity: sha512-EOg8d0jn3BAiKnR55WkFxmxfWA3nmzrbIIuOXyTe6A72duryNgyU+bdBEauA97Aab3ho9kLmAwgPX63Ckj4QEg==} - peerDependencies: - eslint: '>= 8.57.0' - typescript: '>= 5.0.0' - vitest: '*' - peerDependenciesMeta: - typescript: - optional: true - vitest: - optional: true - - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - - '@volar/language-core@2.4.23': - resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} - - '@volar/source-map@2.4.23': - resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} - - '@volar/typescript@2.4.23': - resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - - '@vue/babel-helper-vue-transform-on@1.5.0': - resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} - - '@vue/babel-plugin-jsx@1.5.0': - resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - peerDependenciesMeta: - '@babel/core': - optional: true - - '@vue/babel-plugin-resolve-type@1.5.0': - resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@vue/compiler-core@3.5.20': - resolution: {integrity: sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==} - - '@vue/compiler-dom@3.5.20': - resolution: {integrity: sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==} - - '@vue/compiler-sfc@3.5.20': - resolution: {integrity: sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==} - - '@vue/compiler-ssr@3.5.20': - resolution: {integrity: sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==} - - '@vue/compiler-vue2@2.7.16': - resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - - '@vue/devtools-api@6.6.4': - resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - - '@vue/devtools-api@7.7.7': - resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==} - - '@vue/devtools-core@8.0.1': - resolution: {integrity: sha512-Lf/+ambV3utWJ18r5TnpePbJ60IcIcqeZSQYLyNcFw2sFel0tGMnMyCdDtR1JNIdVZGAVaksTLhGh0FlrNu+sw==} - peerDependencies: - vue: ^3.0.0 - - '@vue/devtools-kit@7.7.7': - resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==} - - '@vue/devtools-kit@8.0.1': - resolution: {integrity: sha512-7kiPhgTKNtNeXltEHnJJjIDlndlJP4P+UJvCw54uVHNDlI6JzwrSiRmW4cxKTug2wDbc/dkGaMnlZghcwV+aWA==} - - '@vue/devtools-shared@7.7.7': - resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==} - - '@vue/devtools-shared@8.0.1': - resolution: {integrity: sha512-PqtWqPPRpMwZ9FjTzyugb5KeV9kmg2C3hjxZHwjl0lijT4QIJDd0z6AWcnbM9w2nayjDymyTt0+sbdTv3pVeNg==} - - '@vue/eslint-config-prettier@10.2.0': - resolution: {integrity: sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==} - peerDependencies: - eslint: '>= 8.21.0' - prettier: '>= 3.0.0' - - '@vue/eslint-config-typescript@14.6.0': - resolution: {integrity: sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^9.10.0 - eslint-plugin-vue: ^9.28.0 || ^10.0.0 - typescript: '>=4.8.4' - peerDependenciesMeta: - typescript: - optional: true - - '@vue/language-core@3.0.6': - resolution: {integrity: sha512-e2RRzYWm+qGm8apUHW1wA5RQxzNhkqbbKdbKhiDUcmMrNAZGyM8aTiL3UrTqkaFI5s7wJRGGrp4u3jgusuBp2A==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@vue/reactivity@3.5.20': - resolution: {integrity: sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==} - - '@vue/runtime-core@3.5.20': - resolution: {integrity: sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==} - - '@vue/runtime-dom@3.5.20': - resolution: {integrity: sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==} - - '@vue/server-renderer@3.5.20': - resolution: {integrity: sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==} - peerDependencies: - vue: 3.5.20 - - '@vue/shared@3.5.20': - resolution: {integrity: sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==} - - '@vue/test-utils@2.4.6': - resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} - - '@vue/tsconfig@0.8.1': - resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==} - peerDependencies: - typescript: 5.x - vue: ^3.4.0 - peerDependenciesMeta: - typescript: - optional: true - vue: - optional: true - - '@vueuse/core@13.8.0': - resolution: {integrity: sha512-rmBcgpEpxY0ZmyQQR94q1qkUcHREiLxQwNyWrtjMDipD0WTH/JBcAt0gdcn2PsH0SA76ec291cHFngmyaBhlxA==} - peerDependencies: - vue: ^3.5.0 - - '@vueuse/metadata@13.8.0': - resolution: {integrity: sha512-BYMp3Gp1kBUPv7AfQnJYP96mkX7g7cKdTIgwv/Jgd+pfQhz678naoZOAcknRtPLP4cFblDDW7rF4e3KFa+PfIA==} - - '@vueuse/shared@13.8.0': - resolution: {integrity: sha512-x4nfM0ykW+RmNJ4/1IzZsuLuWWrNTxlTWUiehTGI54wnOxIgI9EDdu/O5S77ac6hvQ3hk2KpOVFHaM0M796Kbw==} - peerDependencies: - vue: ^3.5.0 - - abbrev@2.0.0: - resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - alien-signals@2.0.7: - resolution: {integrity: sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.0: - resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} - engines: {node: '>=14'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - atob@2.1.2: - resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} - engines: {node: '>= 4.5.0'} - hasBin: true - - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - birpc@2.5.0: - resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.25.3: - resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001737: - resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} - - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - - config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css@3.0.0: - resolution: {integrity: sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - - de-indent@1.0.2: - resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} - engines: {node: '>=18'} - - default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - editorconfig@1.0.4: - resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} - engines: {node: '>=14'} - hasBin: true - - electron-to-chromium@1.5.210: - resolution: {integrity: sha512-20kSVv1tyNBN2VFsjCIJZfyvxqo7ylHPrJLME040f/030lzNMA7uQNpxtqJjWSNpccD8/2sqe53EAjrFPvQmjw==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} - engines: {node: '>=10.13.0'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - error-stack-parser-es@1.0.5: - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.25.9: - resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-oxlint@1.13.0: - resolution: {integrity: sha512-JDLEuxN68RxgYex0HIykEVWiYQvufKvh045lUCwT9Fu3waWTt1syVv/hF5CahrnnRhs2h48FcNcdKB1PAHKFGg==} - - eslint-plugin-playwright@2.2.2: - resolution: {integrity: sha512-j0jKpndIPOXRRP9uMkwb9l/nSmModOU3452nrFdgFJoEv/435J1onk8+aITzjDW8DfypxgmVaDMdmVIa6F7I0w==} - engines: {node: '>=16.6.0'} - peerDependencies: - eslint: '>=8.40.0' - - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-plugin-vue@10.4.0: - resolution: {integrity: sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 - vue-eslint-parser: ^10.0.0 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.34.0: - resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - execa@9.6.0: - resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} - engines: {node: ^18.19.0 || >=20.5.0} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@2.5.1: - resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} - hasBin: true - - js-beautify@1.15.4: - resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} - engines: {node: '>=14'} - hasBin: true - - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsdom@26.1.0: - resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@4.0.0: - resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} - engines: {node: ^18.17.0 || >=20.5.0} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} - engines: {node: '>= 12.0.0'} - - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - magic-string@0.30.18: - resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} - - memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} - engines: {node: '>= 0.10.0'} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} - engines: {node: '>=16 || 14 >=14.17'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} - - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} - engines: {node: ^18 || >=20} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - nopt@7.2.1: - resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - npm-normalize-package-bin@4.0.0: - resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} - engines: {node: ^18.17.0 || >=20.5.0} - - npm-run-all2@8.0.4: - resolution: {integrity: sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==} - engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} - hasBin: true - - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - nwsapi@2.2.21: - resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - oxc-parser@0.74.0: - resolution: {integrity: sha512-2tDN/ttU8WE6oFh8EzKNam7KE7ZXSG5uXmvX85iNzxdJfMssDWcj3gpYzZi1E04XuE7m3v1dVWl/8BE886vPGw==} - engines: {node: '>=20.0.0'} - - oxlint-tsgolint@0.0.4: - resolution: {integrity: sha512-KFWVP+VU3ymgK/Dtuf6iRkqjo+aN42lS1YThY6JWlNi1GQqm7wtio/kAwssqDhm8kP+CVXbgZAtu1wgsK4XeTg==} - hasBin: true - - oxlint@1.13.0: - resolution: {integrity: sha512-wEoHG0WCbxSfpXqrJPbB6q7j16xoiUJD2WHJffpR9CCPB1ZYgOwf/qRSzH9KGW/Uda7oxm/1Ebx4q4hGALJmeQ==} - engines: {node: '>=8.*'} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.0.4' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - perfect-debounce@2.0.0: - resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - - pinia@3.0.3: - resolution: {integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==} - peerDependencies: - typescript: '>=4.4.4' - vue: ^2.7.0 || ^3.5.11 - peerDependenciesMeta: - typescript: - optional: true - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - - playwright-core@1.55.0: - resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.55.0: - resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==} - engines: {node: '>=18'} - hasBin: true - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - - pretty-ms@9.2.0: - resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} - engines: {node: '>=18'} - - primeicons@7.0.0: - resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==} - - primevue@4.3.7: - resolution: {integrity: sha512-yyknh2kFjSGwRyxqmUUWy/pY4FK1vxoonGH3eQdtd8k/sGl5JpSQ2f0tXpR79lV6dcGf3FZPL1B5JNR11zJfmg==} - engines: {node: '>=12.11.0'} - - proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - read-package-json-fast@4.0.0: - resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} - engines: {node: ^18.17.0 || >=20.5.0} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rollup@4.49.0: - resolution: {integrity: sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - - run-applescript@7.0.0: - resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} - engines: {node: '>=18'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sax@1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-resolve@0.6.0: - resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - stylus@0.57.0: - resolution: {integrity: sha512-yOI6G8WYfr0q8v8rRvE91wbxFU+rJPo760Va4MF6K0I6BZjO4r+xSynkvyPBP9tV1CIEUeRsiidjIs2rzb1CnQ==} - hasBin: true - - superjson@2.2.2: - resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} - engines: {node: '>=16'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} - engines: {node: ^14.18.0 || >=16.0.0} - - tailwindcss@4.1.12: - resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} - - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} - engines: {node: '>=6'} - - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} - engines: {node: '>=18'} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tw-animate-css@1.3.7: - resolution: {integrity: sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - typescript-eslint@8.41.0: - resolution: {integrity: sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - - undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - - unplugin-utils@0.2.5: - resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} - engines: {node: '>=18.12.0'} - - unplugin-utils@0.3.0: - resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} - engines: {node: '>=20.19.0'} - - unplugin-vue-components@29.0.0: - resolution: {integrity: sha512-M2DX44g4/jvBkB0V6uwqTbkTd5DMRHpeGoi/cIKwGG4HPuNxLbe8zoTStB2n12hoDiWc9I1PIRQruRWExNXHlQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/parser': ^7.15.8 - '@nuxt/kit': ^3.2.2 || ^4.0.0 - vue: 2 || 3 - peerDependenciesMeta: - '@babel/parser': - optional: true - '@nuxt/kit': - optional: true - - unplugin@2.3.8: - resolution: {integrity: sha512-lkaSIlxceytPyt9yfb1h7L9jDFqwMqvUZeGsKB7Z8QrvAO3xZv2S+xMQQYzxk0AGJHcQhbcvhKEstrMy99jnuQ==} - engines: {node: '>=18.12.0'} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vite-dev-rpc@1.1.0: - resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} - peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 - - vite-hot-client@2.1.0: - resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} - peerDependencies: - vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite-plugin-inspect@11.3.3: - resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} - engines: {node: '>=14'} - peerDependencies: - '@nuxt/kit': '*' - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - '@nuxt/kit': - optional: true - - vite-plugin-vue-devtools@8.0.1: - resolution: {integrity: sha512-ecm/Xvtg5xsFPfY7SJ38Zb6NfmVrHxBhLMk/3nm5ZDAd7n8Dk2BV8JBuq1L5wRMVfvCth01vtzJViZC9TAC6qg==} - engines: {node: '>=v14.21.3'} - peerDependencies: - vite: ^6.0.0 || ^7.0.0-0 - - vite-plugin-vue-inspector@5.3.2: - resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==} - peerDependencies: - vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - - vite@7.1.3: - resolution: {integrity: sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - - vue-component-type-helpers@2.2.12: - resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} - - vue-eslint-parser@10.2.0: - resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - vue-router@4.5.1: - resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} - peerDependencies: - vue: ^3.2.0 - - vue-tsc@3.0.6: - resolution: {integrity: sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==} - hasBin: true - peerDependencies: - typescript: '>=5.0.0' - - vue@3.5.20: - resolution: {integrity: sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - - xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - -snapshots: - - '@alloc/quick-lru@5.2.0': {} - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - - '@asamuzakjp/css-color@3.2.0': - dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 10.4.3 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.0': {} - - '@babel/core@7.28.3': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) - '@babel/helpers': 7.28.3 - '@babel/parser': 7.28.3 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - convert-source-map: 2.0.0 - debug: 4.4.1 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.3': - dependencies: - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.28.2 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.3 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-member-expression-to-functions@7.27.1': - dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.3 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.28.2 - - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.3 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.3': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - - '@babel/parser@7.28.3': - dependencies: - '@babel/types': 7.28.2 - - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.3) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) - transitivePeerDependencies: - - supports-color - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 - - '@babel/traverse@7.28.3': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.3 - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.2': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@chainlift/liftkit@0.2.0': {} - - '@csstools/color-helpers@5.1.0': {} - - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@csstools/normalize.css@12.1.1': {} - - '@emnapi/core@1.4.5': - dependencies: - '@emnapi/wasi-threads': 1.0.4 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.5': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.4': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.25.9': - optional: true - - '@esbuild/android-arm64@0.25.9': - optional: true - - '@esbuild/android-arm@0.25.9': - optional: true - - '@esbuild/android-x64@0.25.9': - optional: true - - '@esbuild/darwin-arm64@0.25.9': - optional: true - - '@esbuild/darwin-x64@0.25.9': - optional: true - - '@esbuild/freebsd-arm64@0.25.9': - optional: true - - '@esbuild/freebsd-x64@0.25.9': - optional: true - - '@esbuild/linux-arm64@0.25.9': - optional: true - - '@esbuild/linux-arm@0.25.9': - optional: true - - '@esbuild/linux-ia32@0.25.9': - optional: true - - '@esbuild/linux-loong64@0.25.9': - optional: true - - '@esbuild/linux-mips64el@0.25.9': - optional: true - - '@esbuild/linux-ppc64@0.25.9': - optional: true - - '@esbuild/linux-riscv64@0.25.9': - optional: true - - '@esbuild/linux-s390x@0.25.9': - optional: true - - '@esbuild/linux-x64@0.25.9': - optional: true - - '@esbuild/netbsd-arm64@0.25.9': - optional: true - - '@esbuild/netbsd-x64@0.25.9': - optional: true - - '@esbuild/openbsd-arm64@0.25.9': - optional: true - - '@esbuild/openbsd-x64@0.25.9': - optional: true - - '@esbuild/openharmony-arm64@0.25.9': - optional: true - - '@esbuild/sunos-x64@0.25.9': - optional: true - - '@esbuild/win32-arm64@0.25.9': - optional: true - - '@esbuild/win32-ia32@0.25.9': - optional: true - - '@esbuild/win32-x64@0.25.9': - optional: true - - '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@2.5.1))': - dependencies: - eslint: 9.34.0(jiti@2.5.1) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/config-array@0.21.0': - dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.1 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.3.1': {} - - '@eslint/core@0.15.2': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.1': - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.34.0': {} - - '@eslint/object-schema@2.1.6': {} - - '@eslint/plugin-kit@0.3.5': - dependencies: - '@eslint/core': 0.15.2 - levn: 0.4.1 - - '@heroicons/vue@2.2.0(vue@3.5.20(typescript@5.9.2))': - dependencies: - vue: 3.5.20(typescript@5.9.2) - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.30': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@material/material-color-utilities@0.3.0': {} - - '@napi-rs/wasm-runtime@0.2.12': - dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 - optional: true - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - - '@one-ini/wasm@0.1.1': {} - - '@oxc-parser/binding-android-arm64@0.74.0': - optional: true - - '@oxc-parser/binding-darwin-arm64@0.74.0': - optional: true - - '@oxc-parser/binding-darwin-x64@0.74.0': - optional: true - - '@oxc-parser/binding-freebsd-x64@0.74.0': - optional: true - - '@oxc-parser/binding-linux-arm-gnueabihf@0.74.0': - optional: true - - '@oxc-parser/binding-linux-arm-musleabihf@0.74.0': - optional: true - - '@oxc-parser/binding-linux-arm64-gnu@0.74.0': - optional: true - - '@oxc-parser/binding-linux-arm64-musl@0.74.0': - optional: true - - '@oxc-parser/binding-linux-riscv64-gnu@0.74.0': - optional: true - - '@oxc-parser/binding-linux-s390x-gnu@0.74.0': - optional: true - - '@oxc-parser/binding-linux-x64-gnu@0.74.0': - optional: true - - '@oxc-parser/binding-linux-x64-musl@0.74.0': - optional: true - - '@oxc-parser/binding-wasm32-wasi@0.74.0': - dependencies: - '@napi-rs/wasm-runtime': 0.2.12 - optional: true - - '@oxc-parser/binding-win32-arm64-msvc@0.74.0': - optional: true - - '@oxc-parser/binding-win32-x64-msvc@0.74.0': - optional: true - - '@oxc-project/types@0.74.0': {} - - '@oxlint-tsgolint/darwin-arm64@0.0.4': - optional: true - - '@oxlint-tsgolint/darwin-x64@0.0.4': - optional: true - - '@oxlint-tsgolint/linux-arm64@0.0.4': - optional: true - - '@oxlint-tsgolint/linux-x64@0.0.4': - optional: true - - '@oxlint-tsgolint/win32-arm64@0.0.4': - optional: true - - '@oxlint-tsgolint/win32-x64@0.0.4': - optional: true - - '@oxlint/darwin-arm64@1.13.0': - optional: true - - '@oxlint/darwin-x64@1.13.0': - optional: true - - '@oxlint/linux-arm64-gnu@1.13.0': - optional: true - - '@oxlint/linux-arm64-musl@1.13.0': - optional: true - - '@oxlint/linux-x64-gnu@1.13.0': - optional: true - - '@oxlint/linux-x64-musl@1.13.0': - optional: true - - '@oxlint/win32-arm64@1.13.0': - optional: true - - '@oxlint/win32-x64@1.13.0': - optional: true - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@pkgr/core@0.2.9': {} - - '@playwright/test@1.55.0': - dependencies: - playwright: 1.55.0 - - '@polka/url@1.0.0-next.29': {} - - '@prettier/plugin-oxc@0.0.4': - dependencies: - oxc-parser: 0.74.0 - - '@primeuix/forms@0.1.0': - dependencies: - '@primeuix/utils': 0.6.1 - - '@primeuix/styled@0.7.2': - dependencies: - '@primeuix/utils': 0.6.1 - - '@primeuix/styles@1.2.3': - dependencies: - '@primeuix/styled': 0.7.2 - - '@primeuix/themes@1.2.3': - dependencies: - '@primeuix/styled': 0.7.2 - - '@primeuix/utils@0.6.1': {} - - '@primevue/auto-import-resolver@4.3.7': - dependencies: - '@primevue/metadata': 4.3.7 - - '@primevue/core@4.3.7(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@primeuix/styled': 0.7.2 - '@primeuix/utils': 0.6.1 - vue: 3.5.20(typescript@5.9.2) - - '@primevue/forms@4.3.7(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@primeuix/forms': 0.1.0 - '@primeuix/utils': 0.6.1 - '@primevue/core': 4.3.7(vue@3.5.20(typescript@5.9.2)) - transitivePeerDependencies: - - vue - - '@primevue/icons@4.3.7(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@primeuix/utils': 0.6.1 - '@primevue/core': 4.3.7(vue@3.5.20(typescript@5.9.2)) - transitivePeerDependencies: - - vue - - '@primevue/metadata@4.3.7': {} - - '@primevue/themes@4.3.7': - dependencies: - '@primeuix/styled': 0.7.2 - '@primeuix/themes': 1.2.3 - - '@rolldown/pluginutils@1.0.0-beta.29': {} - - '@rollup/rollup-android-arm-eabi@4.49.0': - optional: true - - '@rollup/rollup-android-arm64@4.49.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.49.0': - optional: true - - '@rollup/rollup-darwin-x64@4.49.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.49.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.49.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.49.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.49.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.49.0': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.49.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.49.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.49.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.49.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.49.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.49.0': - optional: true - - '@sec-ant/readable-stream@0.4.1': {} - - '@sindresorhus/merge-streams@4.0.0': {} - - '@tailwindcss/node@4.1.12': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - magic-string: 0.30.18 - source-map-js: 1.2.1 - tailwindcss: 4.1.12 - - '@tailwindcss/oxide-android-arm64@4.1.12': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.12': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.12': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.12': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.12': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.12': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.12': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.12': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.12': - optional: true - - '@tailwindcss/oxide@4.1.12': - dependencies: - detect-libc: 2.0.4 - tar: 7.4.3 - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.12 - '@tailwindcss/oxide-darwin-arm64': 4.1.12 - '@tailwindcss/oxide-darwin-x64': 4.1.12 - '@tailwindcss/oxide-freebsd-x64': 4.1.12 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.12 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.12 - '@tailwindcss/oxide-linux-x64-musl': 4.1.12 - '@tailwindcss/oxide-wasm32-wasi': 4.1.12 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.12 - - '@tailwindcss/postcss@4.1.12': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.12 - '@tailwindcss/oxide': 4.1.12 - postcss: 8.5.6 - tailwindcss: 4.1.12 - - '@tailwindcss/vite@4.1.12(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))': - dependencies: - '@tailwindcss/node': 4.1.12 - '@tailwindcss/oxide': 4.1.12 - tailwindcss: 4.1.12 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - - '@tsconfig/node22@22.0.2': {} - - '@tybys/wasm-util@0.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/jsdom@21.1.7': - dependencies: - '@types/node': 24.3.0 - '@types/tough-cookie': 4.0.5 - parse5: 7.3.0 - - '@types/json-schema@7.0.15': {} - - '@types/node@24.3.0': - dependencies: - undici-types: 7.10.0 - - '@types/tough-cookie@4.0.5': {} - - '@types/web-bluetooth@0.0.21': {} - - '@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/type-utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.41.0 - eslint: 9.34.0(jiti@2.5.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.41.0 - debug: 4.4.1 - eslint: 9.34.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.41.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) - '@typescript-eslint/types': 8.41.0 - debug: 4.4.1 - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.41.0': - dependencies: - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/visitor-keys': 8.41.0 - - '@typescript-eslint/tsconfig-utils@8.41.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - - '@typescript-eslint/type-utils@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - debug: 4.4.1 - eslint: 9.34.0(jiti@2.5.1) - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.41.0': {} - - '@typescript-eslint/typescript-estree@8.41.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/project-service': 8.41.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.41.0(typescript@5.9.2) - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/visitor-keys': 8.41.0 - debug: 4.4.1 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.41.0 - '@typescript-eslint/types': 8.41.0 - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - eslint: 9.34.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.41.0': - dependencies: - '@typescript-eslint/types': 8.41.0 - eslint-visitor-keys: 4.2.1 - - '@vitejs/plugin-vue@6.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vue: 3.5.20(typescript@5.9.2) - - '@vitest/eslint-plugin@1.3.4(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)(vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(stylus@0.57.0))': - dependencies: - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.34.0(jiti@2.5.1) - optionalDependencies: - typescript: 5.9.2 - vitest: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(stylus@0.57.0) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@3.2.4': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.2.4(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.18 - optionalDependencies: - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.2.4': - dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.0.0 - - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.18 - pathe: 2.0.3 - - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.3 - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 - - '@volar/language-core@2.4.23': - dependencies: - '@volar/source-map': 2.4.23 - - '@volar/source-map@2.4.23': {} - - '@volar/typescript@2.4.23': - dependencies: - '@volar/language-core': 2.4.23 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - - '@vue/babel-helper-vue-transform-on@1.5.0': {} - - '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.3)': - dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - '@vue/babel-helper-vue-transform-on': 1.5.0 - '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.3) - '@vue/shared': 3.5.20 - optionalDependencies: - '@babel/core': 7.28.3 - transitivePeerDependencies: - - supports-color - - '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.3)': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.3 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/parser': 7.28.3 - '@vue/compiler-sfc': 3.5.20 - transitivePeerDependencies: - - supports-color - - '@vue/compiler-core@3.5.20': - dependencies: - '@babel/parser': 7.28.3 - '@vue/shared': 3.5.20 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.20': - dependencies: - '@vue/compiler-core': 3.5.20 - '@vue/shared': 3.5.20 - - '@vue/compiler-sfc@3.5.20': - dependencies: - '@babel/parser': 7.28.3 - '@vue/compiler-core': 3.5.20 - '@vue/compiler-dom': 3.5.20 - '@vue/compiler-ssr': 3.5.20 - '@vue/shared': 3.5.20 - estree-walker: 2.0.2 - magic-string: 0.30.18 - postcss: 8.5.6 - source-map-js: 1.2.1 - - '@vue/compiler-ssr@3.5.20': - dependencies: - '@vue/compiler-dom': 3.5.20 - '@vue/shared': 3.5.20 - - '@vue/compiler-vue2@2.7.16': - dependencies: - de-indent: 1.0.2 - he: 1.2.0 - - '@vue/devtools-api@6.6.4': {} - - '@vue/devtools-api@7.7.7': - dependencies: - '@vue/devtools-kit': 7.7.7 - - '@vue/devtools-core@8.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@vue/devtools-kit': 8.0.1 - '@vue/devtools-shared': 8.0.1 - mitt: 3.0.1 - nanoid: 5.1.5 - pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - vue: 3.5.20(typescript@5.9.2) - transitivePeerDependencies: - - vite - - '@vue/devtools-kit@7.7.7': - dependencies: - '@vue/devtools-shared': 7.7.7 - birpc: 2.5.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.2 - - '@vue/devtools-kit@8.0.1': - dependencies: - '@vue/devtools-shared': 8.0.1 - birpc: 2.5.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.2 - - '@vue/devtools-shared@7.7.7': - dependencies: - rfdc: 1.4.1 - - '@vue/devtools-shared@8.0.1': - dependencies: - rfdc: 1.4.1 - - '@vue/eslint-config-prettier@10.2.0(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2)': - dependencies: - eslint: 9.34.0(jiti@2.5.1) - eslint-config-prettier: 10.1.8(eslint@9.34.0(jiti@2.5.1)) - eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2) - prettier: 3.6.2 - transitivePeerDependencies: - - '@types/eslint' - - '@vue/eslint-config-typescript@14.6.0(eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.34.0(jiti@2.5.1) - eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))) - fast-glob: 3.3.3 - typescript-eslint: 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - vue-eslint-parser: 10.2.0(eslint@9.34.0(jiti@2.5.1)) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@vue/language-core@3.0.6(typescript@5.9.2)': - dependencies: - '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.20 - '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.20 - alien-signals: 2.0.7 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - picomatch: 4.0.3 - optionalDependencies: - typescript: 5.9.2 - - '@vue/reactivity@3.5.20': - dependencies: - '@vue/shared': 3.5.20 - - '@vue/runtime-core@3.5.20': - dependencies: - '@vue/reactivity': 3.5.20 - '@vue/shared': 3.5.20 - - '@vue/runtime-dom@3.5.20': - dependencies: - '@vue/reactivity': 3.5.20 - '@vue/runtime-core': 3.5.20 - '@vue/shared': 3.5.20 - csstype: 3.1.3 - - '@vue/server-renderer@3.5.20(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@vue/compiler-ssr': 3.5.20 - '@vue/shared': 3.5.20 - vue: 3.5.20(typescript@5.9.2) - - '@vue/shared@3.5.20': {} - - '@vue/test-utils@2.4.6': - dependencies: - js-beautify: 1.15.4 - vue-component-type-helpers: 2.2.12 - - '@vue/tsconfig@0.8.1(typescript@5.9.2)(vue@3.5.20(typescript@5.9.2))': - optionalDependencies: - typescript: 5.9.2 - vue: 3.5.20(typescript@5.9.2) - - '@vueuse/core@13.8.0(vue@3.5.20(typescript@5.9.2))': - dependencies: - '@types/web-bluetooth': 0.0.21 - '@vueuse/metadata': 13.8.0 - '@vueuse/shared': 13.8.0(vue@3.5.20(typescript@5.9.2)) - vue: 3.5.20(typescript@5.9.2) - - '@vueuse/metadata@13.8.0': {} - - '@vueuse/shared@13.8.0(vue@3.5.20(typescript@5.9.2))': - dependencies: - vue: 3.5.20(typescript@5.9.2) - - abbrev@2.0.0: {} - - acorn-jsx@5.3.2(acorn@8.15.0): - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - - agent-base@7.1.4: {} - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - alien-signals@2.0.7: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.1: {} - - ansis@4.1.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@2.0.1: {} - - assertion-error@2.0.1: {} - - atob@2.1.2: - optional: true - - autoprefixer@10.4.21(postcss@8.5.6): - dependencies: - browserslist: 4.25.3 - caniuse-lite: 1.0.30001737 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - balanced-match@1.0.2: {} - - binary-extensions@2.3.0: {} - - birpc@2.5.0: {} - - boolbase@1.0.0: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.25.3: - dependencies: - caniuse-lite: 1.0.30001737 - electron-to-chromium: 1.5.210 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.3) - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.0.0 - - cac@6.7.14: {} - - callsites@3.1.0: {} - - caniuse-lite@1.0.30001737: {} - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - check-error@2.1.1: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@3.0.0: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - commander@10.0.1: {} - - concat-map@0.0.1: {} - - confbox@0.1.8: {} - - confbox@0.2.2: {} - - config-chain@1.1.13: - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - - convert-source-map@2.0.0: {} - - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css@3.0.0: - dependencies: - inherits: 2.0.4 - source-map: 0.6.1 - source-map-resolve: 0.6.0 - optional: true - - cssesc@3.0.0: {} - - cssstyle@4.6.0: - dependencies: - '@asamuzakjp/css-color': 3.2.0 - rrweb-cssom: 0.8.0 - - csstype@3.1.3: {} - - data-urls@5.0.0: - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - - de-indent@1.0.2: {} - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - decimal.js@10.6.0: {} - - decode-uri-component@0.2.2: - optional: true - - deep-eql@5.0.2: {} - - deep-is@0.1.4: {} - - default-browser-id@5.0.0: {} - - default-browser@5.2.1: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.0 - - define-lazy-prop@3.0.0: {} - - detect-libc@2.0.4: {} - - eastasianwidth@0.2.0: {} - - editorconfig@1.0.4: - dependencies: - '@one-ini/wasm': 0.1.1 - commander: 10.0.1 - minimatch: 9.0.1 - semver: 7.7.2 - - electron-to-chromium@1.5.210: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - enhanced-resolve@5.18.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.3 - - entities@4.5.0: {} - - entities@6.0.1: {} - - error-stack-parser-es@1.0.5: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.25.9: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.9 - '@esbuild/android-arm': 0.25.9 - '@esbuild/android-arm64': 0.25.9 - '@esbuild/android-x64': 0.25.9 - '@esbuild/darwin-arm64': 0.25.9 - '@esbuild/darwin-x64': 0.25.9 - '@esbuild/freebsd-arm64': 0.25.9 - '@esbuild/freebsd-x64': 0.25.9 - '@esbuild/linux-arm': 0.25.9 - '@esbuild/linux-arm64': 0.25.9 - '@esbuild/linux-ia32': 0.25.9 - '@esbuild/linux-loong64': 0.25.9 - '@esbuild/linux-mips64el': 0.25.9 - '@esbuild/linux-ppc64': 0.25.9 - '@esbuild/linux-riscv64': 0.25.9 - '@esbuild/linux-s390x': 0.25.9 - '@esbuild/linux-x64': 0.25.9 - '@esbuild/netbsd-arm64': 0.25.9 - '@esbuild/netbsd-x64': 0.25.9 - '@esbuild/openbsd-arm64': 0.25.9 - '@esbuild/openbsd-x64': 0.25.9 - '@esbuild/openharmony-arm64': 0.25.9 - '@esbuild/sunos-x64': 0.25.9 - '@esbuild/win32-arm64': 0.25.9 - '@esbuild/win32-ia32': 0.25.9 - '@esbuild/win32-x64': 0.25.9 - - escalade@3.2.0: {} - - escape-string-regexp@4.0.0: {} - - eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)): - dependencies: - eslint: 9.34.0(jiti@2.5.1) - - eslint-plugin-oxlint@1.13.0: - dependencies: - jsonc-parser: 3.3.1 - - eslint-plugin-playwright@2.2.2(eslint@9.34.0(jiti@2.5.1)): - dependencies: - eslint: 9.34.0(jiti@2.5.1) - globals: 13.24.0 - - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(prettier@3.6.2): - dependencies: - eslint: 9.34.0(jiti@2.5.1) - prettier: 3.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 - optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.34.0(jiti@2.5.1)) - - eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1))): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) - eslint: 9.34.0(jiti@2.5.1) - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.7.2 - vue-eslint-parser: 10.2.0(eslint@9.34.0(jiti@2.5.1)) - xml-name-validator: 4.0.0 - optionalDependencies: - '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint@9.34.0(jiti@2.5.1): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.1 - '@eslint/core': 0.15.2 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.34.0 - '@eslint/plugin-kit': 0.3.5 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.5.1 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - esutils@2.0.3: {} - - execa@9.6.0: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.6 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.1 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.2.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.1.2 - - expect-type@1.2.2: {} - - exsolve@1.0.7: {} - - fast-deep-equal@3.1.3: {} - - fast-diff@1.3.0: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - - flatted@3.3.3: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - fraction.js@4.3.7: {} - - fs.realpath@1.0.0: - optional: true - - fsevents@2.3.2: - optional: true - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - get-stream@9.0.1: - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - optional: true - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globals@14.0.0: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - has-flag@4.0.0: {} - - he@1.2.0: {} - - hookable@5.5.3: {} - - html-encoding-sniffer@4.0.0: - dependencies: - whatwg-encoding: 3.1.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - human-signals@8.0.1: {} - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ignore@5.3.2: {} - - ignore@7.0.5: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - optional: true - - inherits@2.0.4: - optional: true - - ini@1.3.8: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-docker@3.0.0: {} - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-number@7.0.0: {} - - is-plain-obj@4.1.0: {} - - is-potential-custom-element-name@1.0.1: {} - - is-stream@4.0.1: {} - - is-unicode-supported@2.1.0: {} - - is-what@4.1.16: {} - - is-wsl@3.1.0: - dependencies: - is-inside-container: 1.0.0 - - isexe@2.0.0: {} - - isexe@3.1.1: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@2.5.1: {} - - js-beautify@1.15.4: - dependencies: - config-chain: 1.1.13 - editorconfig: 1.0.4 - glob: 10.4.5 - js-cookie: 3.0.5 - nopt: 7.2.1 - - js-cookie@3.0.5: {} - - js-tokens@4.0.0: {} - - js-tokens@9.0.1: {} - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - jsdom@26.1.0: - dependencies: - cssstyle: 4.6.0 - data-urls: 5.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.21 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 5.1.2 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - ws: 8.18.3 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@4.0.0: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json5@2.2.3: {} - - jsonc-parser@3.3.1: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kolorist@1.8.0: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lightningcss-darwin-arm64@1.30.1: - optional: true - - lightningcss-darwin-x64@1.30.1: - optional: true - - lightningcss-freebsd-x64@1.30.1: - optional: true - - lightningcss-linux-arm-gnueabihf@1.30.1: - optional: true - - lightningcss-linux-arm64-gnu@1.30.1: - optional: true - - lightningcss-linux-arm64-musl@1.30.1: - optional: true - - lightningcss-linux-x64-gnu@1.30.1: - optional: true - - lightningcss-linux-x64-musl@1.30.1: - optional: true - - lightningcss-win32-arm64-msvc@1.30.1: - optional: true - - lightningcss-win32-x64-msvc@1.30.1: - optional: true - - lightningcss@1.30.1: - dependencies: - detect-libc: 2.0.4 - optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 - - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 - quansync: 0.2.11 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash-es@4.17.21: {} - - lodash.merge@4.6.2: {} - - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - magic-string@0.30.18: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - memorystream@0.3.1: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.1: - dependencies: - brace-expansion: 2.0.2 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minipass@7.1.2: {} - - minizlib@3.0.2: - dependencies: - minipass: 7.1.2 - - mitt@3.0.1: {} - - mkdirp@3.0.1: {} - - mlly@1.8.0: - dependencies: - acorn: 8.15.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 - - mrmime@2.0.1: {} - - ms@2.1.3: {} - - muggle-string@0.4.1: {} - - nanoid@3.3.11: {} - - nanoid@5.1.5: {} - - natural-compare@1.4.0: {} - - node-releases@2.0.19: {} - - nopt@7.2.1: - dependencies: - abbrev: 2.0.0 - - normalize-path@3.0.0: {} - - normalize-range@0.1.2: {} - - npm-normalize-package-bin@4.0.0: {} - - npm-run-all2@8.0.4: - dependencies: - ansi-styles: 6.2.1 - cross-spawn: 7.0.6 - memorystream: 0.3.1 - picomatch: 4.0.3 - pidtree: 0.6.0 - read-package-json-fast: 4.0.0 - shell-quote: 1.8.3 - which: 5.0.0 - - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - nwsapi@2.2.21: {} - - ohash@2.0.11: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - optional: true - - open@10.2.0: - dependencies: - default-browser: 5.2.1 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - oxc-parser@0.74.0: - dependencies: - '@oxc-project/types': 0.74.0 - optionalDependencies: - '@oxc-parser/binding-android-arm64': 0.74.0 - '@oxc-parser/binding-darwin-arm64': 0.74.0 - '@oxc-parser/binding-darwin-x64': 0.74.0 - '@oxc-parser/binding-freebsd-x64': 0.74.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.74.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.74.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.74.0 - '@oxc-parser/binding-linux-arm64-musl': 0.74.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.74.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.74.0 - '@oxc-parser/binding-linux-x64-gnu': 0.74.0 - '@oxc-parser/binding-linux-x64-musl': 0.74.0 - '@oxc-parser/binding-wasm32-wasi': 0.74.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.74.0 - '@oxc-parser/binding-win32-x64-msvc': 0.74.0 - - oxlint-tsgolint@0.0.4: - optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.0.4 - '@oxlint-tsgolint/darwin-x64': 0.0.4 - '@oxlint-tsgolint/linux-arm64': 0.0.4 - '@oxlint-tsgolint/linux-x64': 0.0.4 - '@oxlint-tsgolint/win32-arm64': 0.0.4 - '@oxlint-tsgolint/win32-x64': 0.0.4 - optional: true - - oxlint@1.13.0(oxlint-tsgolint@0.0.4): - optionalDependencies: - '@oxlint/darwin-arm64': 1.13.0 - '@oxlint/darwin-x64': 1.13.0 - '@oxlint/linux-arm64-gnu': 1.13.0 - '@oxlint/linux-arm64-musl': 1.13.0 - '@oxlint/linux-x64-gnu': 1.13.0 - '@oxlint/linux-x64-musl': 1.13.0 - '@oxlint/win32-arm64': 1.13.0 - '@oxlint/win32-x64': 1.13.0 - oxlint-tsgolint: 0.0.4 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - package-json-from-dist@1.0.1: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-ms@4.0.0: {} - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - path-browserify@1.0.1: {} - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: - optional: true - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - pathe@2.0.3: {} - - pathval@2.0.1: {} - - perfect-debounce@1.0.0: {} - - perfect-debounce@2.0.0: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pidtree@0.6.0: {} - - pinia@3.0.3(typescript@5.9.2)(vue@3.5.20(typescript@5.9.2)): - dependencies: - '@vue/devtools-api': 7.7.7 - vue: 3.5.20(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.0 - pathe: 2.0.3 - - pkg-types@2.3.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.7 - pathe: 2.0.3 - - playwright-core@1.55.0: {} - - playwright@1.55.0: - dependencies: - playwright-core: 1.55.0 - optionalDependencies: - fsevents: 2.3.2 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 - - prettier@3.6.2: {} - - pretty-ms@9.2.0: - dependencies: - parse-ms: 4.0.0 - - primeicons@7.0.0: {} - - primevue@4.3.7(vue@3.5.20(typescript@5.9.2)): - dependencies: - '@primeuix/styled': 0.7.2 - '@primeuix/styles': 1.2.3 - '@primeuix/utils': 0.6.1 - '@primevue/core': 4.3.7(vue@3.5.20(typescript@5.9.2)) - '@primevue/icons': 4.3.7(vue@3.5.20(typescript@5.9.2)) - transitivePeerDependencies: - - vue - - proto-list@1.2.4: {} - - punycode@2.3.1: {} - - quansync@0.2.11: {} - - queue-microtask@1.2.3: {} - - read-package-json-fast@4.0.0: - dependencies: - json-parse-even-better-errors: 4.0.0 - npm-normalize-package-bin: 4.0.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - resolve-from@4.0.0: {} - - reusify@1.1.0: {} - - rfdc@1.4.1: {} - - rollup@4.49.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.49.0 - '@rollup/rollup-android-arm64': 4.49.0 - '@rollup/rollup-darwin-arm64': 4.49.0 - '@rollup/rollup-darwin-x64': 4.49.0 - '@rollup/rollup-freebsd-arm64': 4.49.0 - '@rollup/rollup-freebsd-x64': 4.49.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.49.0 - '@rollup/rollup-linux-arm-musleabihf': 4.49.0 - '@rollup/rollup-linux-arm64-gnu': 4.49.0 - '@rollup/rollup-linux-arm64-musl': 4.49.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.49.0 - '@rollup/rollup-linux-ppc64-gnu': 4.49.0 - '@rollup/rollup-linux-riscv64-gnu': 4.49.0 - '@rollup/rollup-linux-riscv64-musl': 4.49.0 - '@rollup/rollup-linux-s390x-gnu': 4.49.0 - '@rollup/rollup-linux-x64-gnu': 4.49.0 - '@rollup/rollup-linux-x64-musl': 4.49.0 - '@rollup/rollup-win32-arm64-msvc': 4.49.0 - '@rollup/rollup-win32-ia32-msvc': 4.49.0 - '@rollup/rollup-win32-x64-msvc': 4.49.0 - fsevents: 2.3.3 - - rrweb-cssom@0.8.0: {} - - run-applescript@7.0.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safer-buffer@2.1.2: {} - - sax@1.2.4: - optional: true - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - semver@6.3.1: {} - - semver@7.7.2: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shell-quote@1.8.3: {} - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} - - sirv@3.0.1: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - source-map-js@1.2.1: {} - - source-map-resolve@0.6.0: - dependencies: - atob: 2.1.2 - decode-uri-component: 0.2.2 - optional: true - - source-map@0.6.1: - optional: true - - source-map@0.7.6: - optional: true - - speakingurl@14.0.1: {} - - stackback@0.0.2: {} - - std-env@3.9.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.2.0 - - strip-final-newline@4.0.0: {} - - strip-json-comments@3.1.1: {} - - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - - stylus@0.57.0: - dependencies: - css: 3.0.0 - debug: 4.4.1 - glob: 7.2.3 - safer-buffer: 2.1.2 - sax: 1.2.4 - source-map: 0.7.6 - transitivePeerDependencies: - - supports-color - optional: true - - superjson@2.2.2: - dependencies: - copy-anything: 3.0.5 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - symbol-tree@3.2.4: {} - - synckit@0.11.11: - dependencies: - '@pkgr/core': 0.2.9 - - tailwindcss@4.1.12: {} - - tapable@2.2.3: {} - - tar@7.4.3: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 - yallist: 5.0.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.3: {} - - tldts-core@6.1.86: {} - - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - totalist@3.0.1: {} - - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - - ts-api-utils@2.1.0(typescript@5.9.2): - dependencies: - typescript: 5.9.2 - - tslib@2.8.1: - optional: true - - tw-animate-css@1.3.7: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-fest@0.20.2: {} - - typescript-eslint@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.41.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.41.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.34.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - typescript@5.9.2: {} - - ufo@1.6.1: {} - - undici-types@7.10.0: {} - - unicorn-magic@0.3.0: {} - - unplugin-utils@0.2.5: - dependencies: - pathe: 2.0.3 - picomatch: 4.0.3 - - unplugin-utils@0.3.0: - dependencies: - pathe: 2.0.3 - picomatch: 4.0.3 - - unplugin-vue-components@29.0.0(@babel/parser@7.28.3)(vue@3.5.20(typescript@5.9.2)): - dependencies: - chokidar: 3.6.0 - debug: 4.4.1 - local-pkg: 1.1.2 - magic-string: 0.30.18 - mlly: 1.8.0 - tinyglobby: 0.2.14 - unplugin: 2.3.8 - unplugin-utils: 0.2.5 - vue: 3.5.20(typescript@5.9.2) - optionalDependencies: - '@babel/parser': 7.28.3 - transitivePeerDependencies: - - supports-color - - unplugin@2.3.8: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.15.0 - picomatch: 4.0.3 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.1.3(browserslist@4.25.3): - dependencies: - browserslist: 4.25.3 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - util-deprecate@1.0.2: {} - - vite-dev-rpc@1.1.0(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)): - dependencies: - birpc: 2.5.0 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vite-hot-client: 2.1.0(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - - vite-hot-client@2.1.0(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)): - dependencies: - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - - vite-node@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-plugin-inspect@11.3.3(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)): - dependencies: - ansis: 4.1.0 - debug: 4.4.1 - error-stack-parser-es: 1.0.5 - ohash: 2.0.11 - open: 10.2.0 - perfect-debounce: 2.0.0 - sirv: 3.0.1 - unplugin-utils: 0.3.0 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vite-dev-rpc: 1.1.0(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - transitivePeerDependencies: - - supports-color - - vite-plugin-vue-devtools@8.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2)): - dependencies: - '@vue/devtools-core': 8.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0))(vue@3.5.20(typescript@5.9.2)) - '@vue/devtools-kit': 8.0.1 - '@vue/devtools-shared': 8.0.1 - execa: 9.6.0 - sirv: 3.0.1 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vite-plugin-inspect: 11.3.3(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - vite-plugin-vue-inspector: 5.3.2(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - transitivePeerDependencies: - - '@nuxt/kit' - - supports-color - - vue - - vite-plugin-vue-inspector@5.3.2(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)): - dependencies: - '@babel/core': 7.28.3 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) - '@vue/compiler-dom': 3.5.20 - kolorist: 1.8.0 - magic-string: 0.30.18 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - transitivePeerDependencies: - - supports-color - - vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.49.0 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 24.3.0 - fsevents: 2.3.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - stylus: 0.57.0 - - vitest@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(stylus@0.57.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.1 - expect-type: 1.2.2 - magic-string: 0.30.18 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.3.0 - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vscode-uri@3.1.0: {} - - vue-component-type-helpers@2.2.12: {} - - vue-eslint-parser@10.2.0(eslint@9.34.0(jiti@2.5.1)): - dependencies: - debug: 4.4.1 - eslint: 9.34.0(jiti@2.5.1) - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - vue-router@4.5.1(vue@3.5.20(typescript@5.9.2)): - dependencies: - '@vue/devtools-api': 6.6.4 - vue: 3.5.20(typescript@5.9.2) - - vue-tsc@3.0.6(typescript@5.9.2): - dependencies: - '@volar/typescript': 2.4.23 - '@vue/language-core': 3.0.6(typescript@5.9.2) - typescript: 5.9.2 - - vue@3.5.20(typescript@5.9.2): - dependencies: - '@vue/compiler-dom': 3.5.20 - '@vue/compiler-sfc': 3.5.20 - '@vue/runtime-dom': 3.5.20 - '@vue/server-renderer': 3.5.20(vue@3.5.20(typescript@5.9.2)) - '@vue/shared': 3.5.20 - optionalDependencies: - typescript: 5.9.2 - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - webidl-conversions@7.0.0: {} - - webpack-virtual-modules@0.6.2: {} - - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@5.0.0: - dependencies: - isexe: 3.1.1 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - word-wrap@1.2.5: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrappy@1.0.2: - optional: true - - ws@8.18.3: {} - - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.0 - - xml-name-validator@4.0.0: {} - - xml-name-validator@5.0.0: {} - - xmlchars@2.2.0: {} - - yallist@3.1.1: {} - - yallist@5.0.0: {} - - yocto-queue@0.1.0: {} - - yoctocolors@2.1.2: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml deleted file mode 100644 index cd142879..00000000 --- a/frontend/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -onlyBuiltDependencies: - - vue-demi diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index af9d8dc3..00000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - '@tailwindcss/postcss': {}, - autoprefixer: {}, - }, -} \ No newline at end of file diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 419a57a3..00000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,317 +0,0 @@ - - - - - diff --git a/frontend/src/__tests__/App.spec.ts b/frontend/src/__tests__/App.spec.ts deleted file mode 100644 index 5b17801e..00000000 --- a/frontend/src/__tests__/App.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, it, expect } from 'vitest' - -import { mount } from '@vue/test-utils' -import App from '../App.vue' - -describe('App', () => { - it('mounts renders properly', () => { - const wrapper = mount(App) - expect(wrapper.text()).toContain('You did it!') - }) -}) diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue deleted file mode 100644 index ac8b6bf9..00000000 --- a/frontend/src/components/AppSidebar.vue +++ /dev/null @@ -1,387 +0,0 @@ - - - - - diff --git a/frontend/src/components/auth/AuthManager.vue b/frontend/src/components/auth/AuthManager.vue deleted file mode 100644 index 8c6ade4c..00000000 --- a/frontend/src/components/auth/AuthManager.vue +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - diff --git a/frontend/src/components/auth/AuthModal.vue b/frontend/src/components/auth/AuthModal.vue deleted file mode 100644 index ba20fb35..00000000 --- a/frontend/src/components/auth/AuthModal.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - diff --git a/frontend/src/components/auth/ForgotPasswordModal.vue b/frontend/src/components/auth/ForgotPasswordModal.vue deleted file mode 100644 index 61d361fd..00000000 --- a/frontend/src/components/auth/ForgotPasswordModal.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - diff --git a/frontend/src/components/auth/LoginModal.vue b/frontend/src/components/auth/LoginModal.vue deleted file mode 100644 index fb87666d..00000000 --- a/frontend/src/components/auth/LoginModal.vue +++ /dev/null @@ -1,237 +0,0 @@ - - - diff --git a/frontend/src/components/auth/SignupModal.vue b/frontend/src/components/auth/SignupModal.vue deleted file mode 100644 index dfdba1eb..00000000 --- a/frontend/src/components/auth/SignupModal.vue +++ /dev/null @@ -1,335 +0,0 @@ - - - diff --git a/frontend/src/components/button/button.css b/frontend/src/components/button/button.css deleted file mode 100644 index 5d9a7fce..00000000 --- a/frontend/src/components/button/button.css +++ /dev/null @@ -1,87 +0,0 @@ -[data-lk-component='button'] { - /* DEFAULTS */ - --button-font-size: var(--body-font-size); - --button-line-height: var(--lk-halfstep) !important; - --button-padX: var(--button-font-size); - --button-padY: calc( - var(--button-font-size) * calc(var(--lk-halfstep) / var(--lk-size-xl-unitless)) - ); - --button-padX-sideWithIcon: calc(var(--button-font-size) / var(--lk-wholestep)); - --button-gap: calc(var(--button-padY) / var(--lk-eighthstep)); - cursor: pointer; - - display: inline-flex; - vertical-align: middle; - border: 1px solid rgba(0, 0, 0, 0); - border-radius: 100em; - position: relative; - text-decoration: none; - white-space: pre; - word-break: keep-all; - overflow: hidden; - padding: var(--button-padY) 1em; - font-weight: 500; - font-size: var(--button-font-size); - line-height: var(--button-line-height); - font-family: inherit; -} - -/* SIZE VARIANTS */ -[data-lk-button-size='sm'] { - --button-font-size: var(--subheading-font-size); -} - -[data-lk-button-size='lg'] { - --button-font-size: var(--title3-font-size); -} - -/* ICON-BASED PADDING ADJUSTMENTS */ -[data-lk-component='button']:has([data-lk-icon-position='start']) { - padding-left: var(--button-padX-sideWithIcon); - padding-right: var(--button-padX); -} - -[data-lk-component='button']:has([data-lk-icon-position='end']) { - padding-left: 1em; - padding-right: var(--button-padX-sideWithIcon); -} - -[data-lk-component='button']:has([data-lk-icon-position='start']):has( - [data-lk-icon-position='end'] - ) { - padding-left: var(--button-padX-sideWithIcon); - padding-right: var(--button-padX-sideWithIcon); -} - -/* CONTENT WRAPPER */ -[data-lk-button-content-wrap='true'] { - display: inline-flex; - align-items: center; - gap: var(--button-gap); -} - -/* TODO: Remove entirely */ - -/* [data-lk-component="button"] div:has(> [data-lk-component="icon"]) { - width: calc(1em * var(--lk-halfstep)); - aspect-ratio: 1; - display: flex; - align-items: center; - justify-content: center; -} */ - -/* ICON VERTICAL OPTICAL ALIGNMENTS */ - -[data-lk-button-optic-icon-shift='true'] div:has(> [data-lk-component='icon']) { - margin-top: calc(-1 * calc(1em * var(--lk-quarterstep-dec))); -} - -/* STYLE VARIANTS */ - -[data-lk-button-variant='text'] { - background: transparent !important; -} -[data-lk-button-variant='outline'] { - background: transparent !important; - border: 1px solid var(--lk-outlinevariant); -} diff --git a/frontend/src/components/button/index.tsx b/frontend/src/components/button/index.tsx deleted file mode 100644 index 311eec36..00000000 --- a/frontend/src/components/button/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client' - -import { useMemo } from 'react' -import { propsToDataAttrs } from '@/lib/utilities' -import { getOnToken } from '@/lib/colorUtils' -import { IconName } from 'lucide-react/dynamic' -import '@/components/button/button.css' -import StateLayer from '@/components/state-layer' -import { LkStateLayerProps } from '@/components/state-layer' -import Icon from '@/components/icon' - -export interface LkButtonProps extends React.ButtonHTMLAttributes { - label?: string - variant?: 'fill' | 'outline' | 'text' - color?: LkColorWithOnToken - size?: 'sm' | 'md' | 'lg' - material?: string - startIcon?: IconName - endIcon?: IconName - opticIconShift?: boolean - modifiers?: string - stateLayerOverride?: LkStateLayerProps // Optional override for state layer properties -} - -/** - * A customizable button component with support for various visual styles, sizes, and icons. - * - * @param props - The button component props - * @param props.label - The text content displayed inside the button. Defaults to "Button" - * @param props.variant - The visual style variant of the button. Defaults to "fill" - * @param props.color - The color theme of the button. Defaults to "primary" - * @param props.size - The size of the button (sm, md, lg). Defaults to "md" - * @param props.startIcon - Optional icon element to display at the start of the button - * @param props.endIcon - Optional icon element to display at the end of the button - * @param props.restProps - Additional props to be spread to the underlying button element - * @param props.opticIconShift - Boolean to control optical icon alignment on the y-axis. Defaults to true. Pulls icons up slightly. - * @param props.modifiers - Additional class names to concatenate onto the button's default class list - * @param props.stateLayerOverride - Optional override for state layer properties, allowing customization of the state layer's appearance - * - * @returns A styled button element with optional start/end icons and a state layer overlay - * - * @example - * ```tsx - * - ) -} diff --git a/frontend/src/components/entity/AuthPrompt.vue b/frontend/src/components/entity/AuthPrompt.vue deleted file mode 100644 index 41c940ca..00000000 --- a/frontend/src/components/entity/AuthPrompt.vue +++ /dev/null @@ -1,173 +0,0 @@ - - - diff --git a/frontend/src/components/entity/EntitySuggestionCard.vue b/frontend/src/components/entity/EntitySuggestionCard.vue deleted file mode 100644 index 330ade9d..00000000 --- a/frontend/src/components/entity/EntitySuggestionCard.vue +++ /dev/null @@ -1,185 +0,0 @@ - - - - - diff --git a/frontend/src/components/entity/EntitySuggestionManager.vue b/frontend/src/components/entity/EntitySuggestionManager.vue deleted file mode 100644 index 6e38dcb7..00000000 --- a/frontend/src/components/entity/EntitySuggestionManager.vue +++ /dev/null @@ -1,235 +0,0 @@ - - - diff --git a/frontend/src/components/entity/EntitySuggestionModal.vue b/frontend/src/components/entity/EntitySuggestionModal.vue deleted file mode 100644 index 3219d1f0..00000000 --- a/frontend/src/components/entity/EntitySuggestionModal.vue +++ /dev/null @@ -1,214 +0,0 @@ - - - diff --git a/frontend/src/components/entity/index.ts b/frontend/src/components/entity/index.ts deleted file mode 100644 index dab619c6..00000000 --- a/frontend/src/components/entity/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Entity suggestion components -export { default as EntitySuggestionModal } from './EntitySuggestionModal.vue' -export { default as EntitySuggestionCard } from './EntitySuggestionCard.vue' -export { default as AuthPrompt } from './AuthPrompt.vue' - -// Main integration component -export { default as EntitySuggestionManager } from './EntitySuggestionManager.vue' diff --git a/frontend/src/components/filters/ActiveFilterChip.vue b/frontend/src/components/filters/ActiveFilterChip.vue deleted file mode 100644 index 88b776c6..00000000 --- a/frontend/src/components/filters/ActiveFilterChip.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - diff --git a/frontend/src/components/filters/DateRangeFilter.vue b/frontend/src/components/filters/DateRangeFilter.vue deleted file mode 100644 index 0378c165..00000000 --- a/frontend/src/components/filters/DateRangeFilter.vue +++ /dev/null @@ -1,408 +0,0 @@ - - - - - diff --git a/frontend/src/components/filters/FilterSection.vue b/frontend/src/components/filters/FilterSection.vue deleted file mode 100644 index 6e75fc2d..00000000 --- a/frontend/src/components/filters/FilterSection.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/frontend/src/components/filters/PresetItem.vue b/frontend/src/components/filters/PresetItem.vue deleted file mode 100644 index 073c904f..00000000 --- a/frontend/src/components/filters/PresetItem.vue +++ /dev/null @@ -1,209 +0,0 @@ - - - diff --git a/frontend/src/components/filters/RangeFilter.vue b/frontend/src/components/filters/RangeFilter.vue deleted file mode 100644 index 4503abe4..00000000 --- a/frontend/src/components/filters/RangeFilter.vue +++ /dev/null @@ -1,419 +0,0 @@ - - - - - diff --git a/frontend/src/components/filters/RideFilterSidebar.vue b/frontend/src/components/filters/RideFilterSidebar.vue deleted file mode 100644 index d560dde9..00000000 --- a/frontend/src/components/filters/RideFilterSidebar.vue +++ /dev/null @@ -1,935 +0,0 @@ - - - - - diff --git a/frontend/src/components/filters/SavePresetDialog.vue b/frontend/src/components/filters/SavePresetDialog.vue deleted file mode 100644 index c2fedfb1..00000000 --- a/frontend/src/components/filters/SavePresetDialog.vue +++ /dev/null @@ -1,390 +0,0 @@ -