akoulapure commited on
Commit
228add1
·
verified ·
1 Parent(s): 10c689e

Initial Deployment: Best ViT Model

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +15 -35
  2. .gitignore +34 -0
  3. .slugignore.txt +4 -0
  4. Dockerfile +46 -0
  5. LICENSE +21 -0
  6. Procfile +1 -0
  7. README.md +121 -5
  8. api/index.py +11 -0
  9. artifacts.zip +3 -0
  10. backend/Dockerfile +31 -0
  11. backend/__init__.py +0 -0
  12. backend/app/__init__.py +0 -0
  13. backend/app/api/__init__.py +0 -0
  14. backend/app/api/routes/__init__.py +0 -0
  15. backend/app/api/routes/health.py +32 -0
  16. backend/app/api/routes/metadata.py +57 -0
  17. backend/app/api/routes/predict.py +94 -0
  18. backend/app/core/__init__.py +0 -0
  19. backend/app/core/config.py +61 -0
  20. backend/app/core/logging.py +27 -0
  21. backend/app/main.py +119 -0
  22. backend/app/schemas/__init__.py +0 -0
  23. backend/app/schemas/breed.py +36 -0
  24. backend/app/schemas/predict.py +52 -0
  25. backend/app/services/__init__.py +0 -0
  26. backend/app/services/breed_info.py +92 -0
  27. backend/app/services/image_loader.py +60 -0
  28. backend/app/services/inference.py +196 -0
  29. backend/requirements.txt +14 -0
  30. colab_run_all.ipynb +286 -0
  31. data/breed_metadata.csv +27 -0
  32. deploy.py +76 -0
  33. docker-compose.yml +39 -0
  34. frontend/.gitignore +24 -0
  35. frontend/Dockerfile +15 -0
  36. frontend/README.md +16 -0
  37. frontend/dist/assets/index-CWZymrby.css +1 -0
  38. frontend/dist/assets/index-sBN3tCmx.js +0 -0
  39. frontend/dist/favicon.svg +1 -0
  40. frontend/dist/icons.svg +24 -0
  41. frontend/dist/index.html +14 -0
  42. frontend/eslint.config.js +29 -0
  43. frontend/index.html +13 -0
  44. frontend/nginx.conf +24 -0
  45. frontend/package-lock.json +2625 -0
  46. frontend/package.json +28 -0
  47. frontend/public/favicon.svg +1 -0
  48. frontend/public/icons.svg +24 -0
  49. frontend/src/App.css +1 -0
  50. frontend/src/App.jsx +42 -0
.gitattributes CHANGED
@@ -1,35 +1,15 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ .pkl filter=lfs diff=lfs merge=lfs -text
2
+ artifacts.zip filter=lfs diff=lfs merge=lfs -text
3
+ ml/artifacts/figures/class_distribution.png filter=lfs diff=lfs merge=lfs -text
4
+ ml/artifacts/figures/cnn/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
5
+ ml/artifacts/figures/cnn/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
6
+ ml/artifacts/figures/cnn/training_curves.png filter=lfs diff=lfs merge=lfs -text
7
+ ml/artifacts/figures/comparison/comparison_radar.png filter=lfs diff=lfs merge=lfs -text
8
+ ml/artifacts/figures/mlp/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
9
+ ml/artifacts/figures/mlp/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
10
+ ml/artifacts/figures/mlp/training_curves.png filter=lfs diff=lfs merge=lfs -text
11
+ ml/artifacts/figures/resnet/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
12
+ ml/artifacts/figures/resnet/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
13
+ ml/artifacts/figures/sample_images.png filter=lfs diff=lfs merge=lfs -text
14
+ ml/artifacts/figures/vit/confusion_matrix.png filter=lfs diff=lfs merge=lfs -text
15
+ ml/artifacts/figures/vit/per_class_f1.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model checkpoints & artifacts
2
+ ml/artifacts/checkpoints/*.pth
3
+ ml/artifacts/checkpoints/*.pt
4
+ ml/artifacts/checkpoints/*.ckpt
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+ *.egg
14
+
15
+ # Virtual environments
16
+ venv/
17
+ env/
18
+ .env
19
+
20
+ # Jupyter
21
+ .ipynb_checkpoints/
22
+
23
+ # OS files
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # IDE
28
+ .vscode/
29
+ .idea/
30
+
31
+ backend.log
32
+ tunnel.log
33
+
34
+ models/*.pth
.slugignore.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ *.psd
2
+ *.pdf
3
+ /test
4
+ /spec
Dockerfile ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unified Dockerfile for Hugging Face Spaces (or Render)
2
+ # Builds the Vite React Frontend and serves it statically via FastAPI Backend
3
+
4
+ # Stage 1: Build Frontend
5
+ FROM node:20-alpine as frontend-builder
6
+ WORKDIR /app/frontend
7
+ COPY frontend/package*.json ./
8
+ RUN npm ci
9
+ COPY frontend/ ./
10
+ RUN npm run build
11
+
12
+ # Stage 2: Build Backend & Serve
13
+ FROM python:3.11-slim
14
+
15
+ WORKDIR /app
16
+
17
+ # Install system dependencies
18
+ RUN apt-get update && apt-get install -y --no-install-recommends \
19
+ gcc \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Copy backend requirements and install
23
+ COPY backend/requirements.txt ./backend/
24
+ RUN pip install --no-cache-dir -r backend/requirements.txt
25
+
26
+ # Copy backend source code and config
27
+ COPY backend/ ./backend/
28
+ COPY ml/src/ ./ml/src/
29
+ COPY data/breed_metadata.csv ./data/
30
+ # Copy the built frontend
31
+ COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
32
+ # Copy the exported model artifact
33
+ COPY models/vit_best.pth ./models/vit_best.pth
34
+
35
+ # Set runtime environments
36
+ ENV PYTHONPATH=/app
37
+ ENV MODEL_PATH=/app/models/vit_best.pth
38
+ ENV METADATA_PATH=/app/data/breed_metadata.csv
39
+ ENV MODEL_NAME=vit
40
+ ENV MODEL_VERSION=v2.0
41
+
42
+ # Hugging Face Spaces run explicitly on 7860
43
+ EXPOSE 7860
44
+
45
+ # Run Uvicorn via HTTP (Hugging Face / Render friendly)
46
+ CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "7860"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ajaya and team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: python app.py
README.md CHANGED
@@ -1,10 +1,126 @@
1
  ---
2
- title: Cattle Classiier Code
3
- emoji:
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Cattle Breed Classifier
3
+ emoji: 🐄
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ # 🐄 Indigenous Cattle Breed Classifier
11
+
12
+ Identifying indigenous Indian cattle and buffalo breeds requires specialized domain expertise that many farmers and agricultural workers may lack. Proper identification is critical for optimizing milk yield projections, managing livestock lifespans, and preserving regional biodiversity.
13
+
14
+ This project solves this by delivering an end-to-end deep learning pipeline and an accessible web application capable of identifying **26 distinct indigenous cattle and buffalo breeds** from an image.
15
+
16
+ ![Python](https://img.shields.io/badge/Python-3.11-blue) ![PyTorch](https://img.shields.io/badge/PyTorch-2.x-red) ![FastAPI](https://img.shields.io/badge/FastAPI-0.104-green) ![React](https://img.shields.io/badge/React-18-61DAFB) ![License](https://img.shields.io/badge/License-MIT-yellow)
17
+
18
+ ### 🟢 Live Demo
19
+ **Test the fully deployed Vision Transformer natively in the browser here:**
20
+ 👉 **[https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier](https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier)**
21
+
22
+ ---
23
+
24
+ ## 📊 Data Gathering & Information
25
+
26
+ The foundation of any robust computer vision model is its data. Our dataset comprises thousands of images distributed across 26 target classes, which include **21 Cow breeds** and **5 Buffalo breeds**.
27
+
28
+ To handle raw data efficiently, we developed a dynamic pre-processing layer that structurally manages unbalanced classes, unifies image ratios, and performs aggressive augmentation.
29
+
30
+ <p align="center">
31
+ <img src="ml/artifacts/figures/class_distribution.png" width="48%" />
32
+ <img src="ml/artifacts/figures/sample_images.png" width="48%" />
33
+ </p>
34
+
35
+ *Above: Analyzing the inherent class imbalance constraints of the dataset and some augmented examples.*
36
+
37
+ ---
38
+
39
+ ## 🧠 Models Trained: Pros & Cons
40
+
41
+ Rather than settling for one model, we evaluated 4 widely different paradigms for image classification. By building a unified, automated training pipeline config, we systematically benchmarked them against one another.
42
+
43
+ ### 1. Multilayer Perceptron (MLP)
44
+ * **The Idea:** Linear spatial flattening.
45
+ * **Pros:** Theoretically the simplest and most interpretable neural structure.
46
+ * **Cons:** Explodes in parameter size (**~590 MB** footprint) and lacks any spatial or localized awareness, resulting in very **poor accuracy (~24%)**.
47
+
48
+ ### 2. CNN (From Scratch)
49
+ * **The Idea:** A custom 5-block Convolutional Neural Network built from zero.
50
+ * **Pros:** Learns specialized local spatial features natively on the data. Highly lightweight (**18.5 MB**) and blazing fast (**2.4ms latency**).
51
+ * **Cons:** Training a CNN purely from scratch on limited agricultural data yields heavily under-fitted results, hovering around **~24.4% accuracy**.
52
+
53
+ ### 3. ResNet-50 (Transfer Learning)
54
+ * **The Idea:** Leverages massive prior knowledge using a pre-trained ImageNet model, unfreezing the dense classifier layers natively for cattle variants.
55
+ * **Pros:** Phenomenal balance of inference time and capability. Shoots accuracy up drastically (**63.2%**) while maintaining a manageable footprint (**93 MB**).
56
+ * **Cons:** It loses on sheer simplicity when compared to the CNN.
57
+
58
+ ### 4. Vision Transformer (ViT-B/16)
59
+ * **The Idea:** Treats image patches like NLP tokens, learning global context via multi-headed self-attention.
60
+ * **Pros:** Tied for the absolute greatest capability and contextual representation capability (**63.2%** accuracy).
61
+ * **Cons:** Major overkill for rapid, on-device basic inference. It is the largest model (**328 MB**) with the slowest inference ceiling (**15.5ms**).
62
+
63
+ ---
64
+
65
+ ## 📈 Model Comparison
66
+
67
+ Based on our automated scoring system—which weighs 50% F1, 20% Accuracy, 15% Latency, and 10% Size—the optimal deployment choice falls between **ResNet-50** or **ViT**.
68
+
69
+ | Architecture | Accuracy | Macro F1 | Latency (ms) | Size (MB) |
70
+ |---|---|---|---|---|
71
+ | **CNN** | 24.4% | 14.0% | 2.4 | 18.5 |
72
+ | **MLP** | 24.0% | 13.0% | 2.5 | 590.5 |
73
+ | **ResNet-50** | **63.2%** | **56.0%** | 5.7 | 93.7 |
74
+ | **ViT-B/16** | **63.2%** | 55.0% | 15.5 | 328.9 |
75
+
76
+ <p align="center">
77
+ <img src="ml/artifacts/figures/comparison/comparison_bar.png" width="48%" />
78
+ <img src="ml/artifacts/figures/comparison/comparison_radar.png" width="48%" />
79
+ </p>
80
+
81
+ ---
82
+
83
+ ## 🚀 Deployment Pipeline
84
+
85
+ This application leverages a completely unified, containerized deployment mechanic utilizing Docker.
86
+
87
+ ### Running Natively (FastAPI Unified Server)
88
+ For zero-dependency deployment, we bundle the built React Static files and serve them natively over the FastAPI Python server.
89
+ ```bash
90
+ # 1. Build the frontend (Node 20+)
91
+ cd frontend && npm install && npm run build
92
+ cd ..
93
+
94
+ # 2. Start the unified server mapping the optimal architecture
95
+ export MODEL_PATH=models/vit_best.pth MODEL_NAME=vit
96
+ python3 -m pip install -r backend/requirements.txt
97
+ uvicorn backend.app.main:app --host 0.0.0.0 --port 7860
98
+ ```
99
+ Then navigate to **http://localhost:7860**.
100
+
101
+ ### Active Cloud Deployment (Hugging Face Spaces)
102
+ The environment has been securely deployed to Hugging Face Spaces via an automated API deployer mapping our unified `Dockerfile`. The space automatically bakes in the Node.js compiled frontend, caches the 460MB ViT model, and publicly maps the server securely.
103
+
104
+ **Access it here:** [https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier](https://huggingface.co/spaces/amoghakoulapure/cattle-breed-classifier)
105
+
106
+ ---
107
+
108
+ ## ⚡ How to Train in Colab
109
+
110
+ If you want to train these models yourself utilizing Free T4 GPUs, we designed an automated Google Colab integration:
111
+
112
+ 1. Upload or Open `colab_run_all.ipynb` in Google Colab.
113
+ 2. Set Runtime to **T4 GPU**.
114
+ 3. **Run All Cells**. The script will automatically:
115
+ * Clone this code repository.
116
+ * Auto-fetch all images natively into the drive.
117
+ * Cycle-train through `00_data_audit`, `01_mlp`, `02_cnn`, `03_resnet`, `04_vit`, and finally execution benchmarks.
118
+ * Zip and download `--log-output` evaluation artifacts and new `.pth` files straight back to your computer dynamically!
119
+
120
+ ---
121
+
122
+ ### Breeds Supported (26)
123
+
124
+ **Cows (21)**: Alambadi, Amritmahal, Bargur, Dangi, Deoni, Gir, Hallikar, Kangayam, Kankrej, Kasaragod, Kenkatha, Kherigarh, Malnad Gidda, Nagori, Nimari, Pulikulam, Rathi, Sahiwal, Tharparkar, Umblachery
125
+
126
+ **Buffaloes (5)**: Banni, Jaffrabadi, Mehsana, Nagpuri, Nili Ravi, Shurti
api/index.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ # Add project root to python path to allow backend package imports
5
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent
6
+ sys.path.insert(0, str(PROJECT_ROOT))
7
+
8
+ # Import the FastAPI application
9
+ from backend.app.main import app
10
+
11
+ # Vercel Serverless requires the application to be exposed as `app`
artifacts.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4fb61dc3352333e5e05a2c3ce96cacd94dd15cdc3eae04a8412ab9716157f921
3
+ size 254
backend/Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend Dockerfile
2
+ FROM python:3.11-slim
3
+
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ gcc \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements first for layer caching
12
+ COPY backend/requirements.txt ./backend/requirements.txt
13
+ RUN pip install --no-cache-dir -r backend/requirements.txt
14
+
15
+ # Copy project files
16
+ COPY backend/ ./backend/
17
+ COPY ml/ ./ml/
18
+ COPY data/ ./data/
19
+ COPY models/ ./models/
20
+
21
+ # Set environment variables
22
+ ENV PYTHONPATH=/app
23
+ ENV MODEL_PATH=/app/models/vit_best.pth
24
+ ENV METADATA_PATH=/app/data/breed_metadata.csv
25
+ ENV CLASSES_PATH=/app/models/classes.txt
26
+ ENV MODEL_NAME=vit
27
+ ENV MODEL_VERSION=v2.0
28
+
29
+ EXPOSE 8000
30
+
31
+ CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
backend/__init__.py ADDED
File without changes
backend/app/__init__.py ADDED
File without changes
backend/app/api/__init__.py ADDED
File without changes
backend/app/api/routes/__init__.py ADDED
File without changes
backend/app/api/routes/health.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Health and version endpoints.
3
+ """
4
+
5
+ from fastapi import APIRouter
6
+
7
+ from backend.app.core.config import get_settings
8
+ from backend.app.services.inference import inference_service
9
+
10
+ router = APIRouter(tags=["Health"])
11
+
12
+
13
+ @router.get("/health")
14
+ async def health():
15
+ """Health check endpoint."""
16
+ return {
17
+ "status": "healthy",
18
+ "model_loaded": inference_service.is_loaded,
19
+ }
20
+
21
+
22
+ @router.get("/version")
23
+ async def version():
24
+ """API version info."""
25
+ settings = get_settings()
26
+ return {
27
+ "app_name": settings.app_name,
28
+ "app_version": settings.app_version,
29
+ "model_version": settings.model_version,
30
+ "model_name": settings.model_name,
31
+ "num_classes": len(inference_service.class_names),
32
+ }
backend/app/api/routes/metadata.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Breed metadata endpoints.
3
+ GET /breeds - List all breeds
4
+ GET /breeds/{name} - Get breed details
5
+ """
6
+
7
+ from fastapi import APIRouter, HTTPException, Query
8
+ from typing import Optional
9
+
10
+ from backend.app.schemas.breed import BreedDetail, BreedListItem, BreedsListResponse
11
+ from backend.app.services.breed_info import breed_info_service
12
+
13
+ router = APIRouter(prefix="/breeds", tags=["Breeds"])
14
+
15
+
16
+ @router.get("", response_model=BreedsListResponse)
17
+ async def list_breeds(
18
+ animal_type: Optional[str] = Query(None, description="Filter by animal type (Cow/Buffalo)"),
19
+ search: Optional[str] = Query(None, description="Search by breed name"),
20
+ ):
21
+ """List all breeds with optional filtering."""
22
+ breeds = breed_info_service.get_all()
23
+
24
+ if animal_type:
25
+ breeds = [b for b in breeds if b['animal_type'].lower() == animal_type.lower()]
26
+
27
+ if search:
28
+ search_lower = search.lower()
29
+ breeds = [b for b in breeds if search_lower in b['breed_name'].lower()]
30
+
31
+ items = [
32
+ BreedListItem(
33
+ breed_id=b['breed_id'],
34
+ breed_name=b['breed_name'],
35
+ animal_type=b['animal_type'],
36
+ region=b['region'],
37
+ primary_use=b['primary_use'],
38
+ )
39
+ for b in breeds
40
+ ]
41
+
42
+ return BreedsListResponse(total=len(items), breeds=items)
43
+
44
+
45
+ @router.get("/{breed_name}", response_model=BreedDetail)
46
+ async def get_breed(breed_name: str):
47
+ """Get detailed info for a specific breed."""
48
+ info = breed_info_service.get_breed(breed_name)
49
+ if info is None:
50
+ # Try case-insensitive search
51
+ results = breed_info_service.search(breed_name)
52
+ if results:
53
+ info = results[0]
54
+ else:
55
+ raise HTTPException(status_code=404, detail=f"Breed not found: {breed_name}")
56
+
57
+ return BreedDetail(**info)
backend/app/api/routes/predict.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Prediction endpoints.
3
+ POST /predict/file - Upload image file
4
+ POST /predict/url - Predict from image URL
5
+ POST /predict/base64 - Predict from base64 image
6
+ """
7
+
8
+ from fastapi import APIRouter, UploadFile, File, HTTPException, Query
9
+
10
+ from backend.app.schemas.predict import (
11
+ PredictResponse,
12
+ PredictURLRequest,
13
+ PredictBase64Request,
14
+ PredictionItem,
15
+ BreedInfo,
16
+ )
17
+ from backend.app.services.inference import inference_service
18
+ from backend.app.services.image_loader import (
19
+ load_image_from_upload,
20
+ load_image_from_url,
21
+ load_image_from_base64,
22
+ )
23
+ from backend.app.services.breed_info import breed_info_service
24
+ from backend.app.core.logging import logger
25
+
26
+ router = APIRouter(prefix="/predict", tags=["Prediction"])
27
+
28
+
29
+ def _build_response(result: dict) -> PredictResponse:
30
+ """Build a PredictResponse from inference result dict."""
31
+ # Get breed info
32
+ breed_info = None
33
+ breed_summary = breed_info_service.get_breed_summary(result['predicted_breed'])
34
+ if breed_summary:
35
+ breed_info = BreedInfo(**breed_summary)
36
+
37
+ return PredictResponse(
38
+ predicted_breed=result['predicted_breed'],
39
+ confidence=result['confidence'],
40
+ top_k=[PredictionItem(**item) for item in result['top_k']],
41
+ breed_info=breed_info,
42
+ model_version=result.get('model_version', 'v1.0'),
43
+ inference_time_ms=result['inference_time_ms'],
44
+ warning=result.get('warning'),
45
+ )
46
+
47
+
48
+ @router.post("/file", response_model=PredictResponse)
49
+ async def predict_file(
50
+ file: UploadFile = File(...),
51
+ top_k: int = Query(default=3, ge=1, le=10),
52
+ ):
53
+ """Predict breed from uploaded image file."""
54
+
55
+ try:
56
+ contents = await file.read()
57
+ image = load_image_from_upload(contents)
58
+ result = inference_service.predict(image, top_k=top_k)
59
+ return _build_response(result)
60
+ except ValueError as e:
61
+ raise HTTPException(status_code=400, detail=str(e))
62
+ except Exception as e:
63
+ logger.error(f"Prediction error: {e}")
64
+ raise HTTPException(status_code=500, detail="Prediction failed")
65
+
66
+
67
+ @router.post("/url", response_model=PredictResponse)
68
+ async def predict_url(request: PredictURLRequest):
69
+ """Predict breed from image URL."""
70
+
71
+ try:
72
+ image = load_image_from_url(request.url)
73
+ result = inference_service.predict(image, top_k=request.top_k)
74
+ return _build_response(result)
75
+ except ValueError as e:
76
+ raise HTTPException(status_code=400, detail=str(e))
77
+ except Exception as e:
78
+ logger.error(f"Prediction error: {e}")
79
+ raise HTTPException(status_code=500, detail="Prediction failed")
80
+
81
+
82
+ @router.post("/base64", response_model=PredictResponse)
83
+ async def predict_base64(request: PredictBase64Request):
84
+ """Predict breed from base64-encoded image."""
85
+
86
+ try:
87
+ image = load_image_from_base64(request.image)
88
+ result = inference_service.predict(image, top_k=request.top_k)
89
+ return _build_response(result)
90
+ except ValueError as e:
91
+ raise HTTPException(status_code=400, detail=str(e))
92
+ except Exception as e:
93
+ logger.error(f"Prediction error: {e}")
94
+ raise HTTPException(status_code=500, detail="Prediction failed")
backend/app/core/__init__.py ADDED
File without changes
backend/app/core/config.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Application configuration using Pydantic Settings.
3
+ Reads from environment variables and .env file.
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from functools import lru_cache
9
+
10
+ from pydantic_settings import BaseSettings
11
+
12
+
13
+ class Settings(BaseSettings):
14
+ """Application configuration from environment variables."""
15
+
16
+ # App
17
+ app_name: str = "Cattle Breed Classifier API"
18
+ app_version: str = "1.0.0"
19
+ debug: bool = False
20
+
21
+ # Model
22
+ model_path: str = os.environ.get(
23
+ "MODEL_PATH",
24
+ str(Path(__file__).resolve().parents[3] / "models" / "cattle_breed_classifier_full_model.pth")
25
+ )
26
+ model_name: str = os.environ.get("MODEL_NAME", "resnet")
27
+ model_version: str = os.environ.get("MODEL_VERSION", "v1.0")
28
+
29
+ # Metadata
30
+ metadata_path: str = os.environ.get(
31
+ "METADATA_PATH",
32
+ str(Path(__file__).resolve().parents[3] / "data" / "breed_metadata.csv")
33
+ )
34
+ classes_path: str = os.environ.get(
35
+ "CLASSES_PATH",
36
+ str(Path(__file__).resolve().parents[3] / "models" / "classes.txt")
37
+ )
38
+
39
+ # Image
40
+ image_size: int = 224
41
+ max_image_size_mb: float = 10.0
42
+
43
+ # Server
44
+ host: str = "0.0.0.0"
45
+ port: int = 8000
46
+ cors_origins: list[str] = ["*"]
47
+
48
+ # Inference
49
+ top_k: int = 3
50
+ url_timeout: int = 10
51
+
52
+ class Config:
53
+ env_file = ".env"
54
+ env_file_encoding = "utf-8"
55
+ case_sensitive = False
56
+
57
+
58
+ @lru_cache()
59
+ def get_settings() -> Settings:
60
+ """Get cached settings instance."""
61
+ return Settings()
backend/app/core/logging.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Structured logging setup.
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+
8
+
9
+ def setup_logging(level: str = "INFO") -> logging.Logger:
10
+ """Configure application logging."""
11
+ logger = logging.getLogger("cattle_classifier")
12
+ logger.setLevel(getattr(logging, level.upper(), logging.INFO))
13
+
14
+ if not logger.handlers:
15
+ handler = logging.StreamHandler(sys.stdout)
16
+ handler.setLevel(logging.DEBUG)
17
+ formatter = logging.Formatter(
18
+ "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
19
+ datefmt="%Y-%m-%d %H:%M:%S",
20
+ )
21
+ handler.setFormatter(formatter)
22
+ logger.addHandler(handler)
23
+
24
+ return logger
25
+
26
+
27
+ logger = setup_logging()
backend/app/main.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI main application.
3
+ Cattle Breed Classifier API.
4
+ """
5
+
6
+ import sys
7
+ from pathlib import Path
8
+ from contextlib import asynccontextmanager
9
+
10
+ from fastapi import FastAPI
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+
13
+ # Ensure project root is in path
14
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
15
+ sys.path.insert(0, str(PROJECT_ROOT))
16
+
17
+ from backend.app.core.config import get_settings
18
+ from backend.app.core.logging import logger
19
+ from backend.app.api.routes import health, predict, metadata
20
+ from backend.app.services.inference import inference_service
21
+ from backend.app.services.breed_info import breed_info_service
22
+
23
+
24
+ @asynccontextmanager
25
+ async def lifespan(app: FastAPI):
26
+ """Startup and shutdown events."""
27
+ # Startup
28
+ logger.info("Starting Cattle Breed Classifier API...")
29
+ try:
30
+ inference_service.load()
31
+ breed_info_service.load()
32
+ logger.info("All services loaded successfully")
33
+ except Exception as e:
34
+ logger.error(f"Startup error: {e}")
35
+ logger.info("API started with limited functionality")
36
+
37
+ yield
38
+
39
+ # Shutdown
40
+ logger.info("Shutting down...")
41
+
42
+
43
+ def create_app() -> FastAPI:
44
+ """Create and configure FastAPI application."""
45
+ settings = get_settings()
46
+
47
+ app = FastAPI(
48
+ title=settings.app_name,
49
+ version=settings.app_version,
50
+ description=(
51
+ "API for classifying Indian cattle breeds using deep learning. "
52
+ "Supports image upload, URL, and base64 input. "
53
+ "Returns breed prediction with confidence and metadata."
54
+ ),
55
+ lifespan=lifespan,
56
+ docs_url="/docs",
57
+ redoc_url="/redoc",
58
+ )
59
+
60
+ # CORS
61
+ app.add_middleware(
62
+ CORSMiddleware,
63
+ allow_origins=settings.cors_origins,
64
+ allow_credentials=True,
65
+ allow_methods=["*"],
66
+ allow_headers=["*"],
67
+ )
68
+
69
+ # Include routers
70
+ app.include_router(health.router)
71
+ app.include_router(predict.router)
72
+ app.include_router(metadata.router)
73
+
74
+ # Serve Static frontend if it exists
75
+ static_dir = PROJECT_ROOT / "frontend" / "dist"
76
+ if static_dir.exists() and static_dir.is_dir():
77
+ from fastapi.staticfiles import StaticFiles
78
+ from fastapi.responses import FileResponse
79
+
80
+ # Mount assets
81
+ assets_dir = static_dir / "assets"
82
+ if assets_dir.exists():
83
+ app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
84
+
85
+ @app.get("/", tags=["Root"])
86
+ async def root():
87
+ return FileResponse(str(static_dir / "index.html"))
88
+
89
+ # Catch-all for react router
90
+ @app.get("/{full_path:path}", tags=["Root"])
91
+ async def serve_spa(full_path: str):
92
+ # Exclude api requests
93
+ if full_path.startswith("api/") or full_path.startswith("docs") or full_path.startswith("redoc"):
94
+ return {"detail": "Not Found"}
95
+ return FileResponse(str(static_dir / "index.html"))
96
+ else:
97
+ @app.get("/", tags=["Root"])
98
+ async def root():
99
+ return {
100
+ "message": "Cattle Breed Classifier API",
101
+ "version": settings.app_version,
102
+ "docs": "/docs",
103
+ }
104
+
105
+ return app
106
+
107
+
108
+ app = create_app()
109
+
110
+
111
+ if __name__ == "__main__":
112
+ import uvicorn
113
+ settings = get_settings()
114
+ uvicorn.run(
115
+ "backend.app.main:app",
116
+ host=settings.host,
117
+ port=settings.port,
118
+ reload=settings.debug,
119
+ )
backend/app/schemas/__init__.py ADDED
File without changes
backend/app/schemas/breed.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pydantic schemas for breed metadata endpoints.
3
+ """
4
+
5
+ from typing import Optional
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class BreedDetail(BaseModel):
10
+ """Full breed detail."""
11
+ breed_id: int
12
+ breed_name: str
13
+ animal_type: str
14
+ region: str
15
+ avg_milk_liters_per_day: str
16
+ lifespan_years: str
17
+ primary_use: str
18
+ coat_color_notes: str
19
+ horn_notes: str
20
+ description: str
21
+ source_reference: str
22
+
23
+
24
+ class BreedListItem(BaseModel):
25
+ """Summary breed item for list endpoints."""
26
+ breed_id: int
27
+ breed_name: str
28
+ animal_type: str
29
+ region: str
30
+ primary_use: str
31
+
32
+
33
+ class BreedsListResponse(BaseModel):
34
+ """Response for listing all breeds."""
35
+ total: int
36
+ breeds: list[BreedListItem]
backend/app/schemas/predict.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pydantic schemas for prediction endpoints.
3
+ """
4
+
5
+ from typing import Optional
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class PredictionItem(BaseModel):
10
+ """Single prediction result."""
11
+ breed: str = Field(..., description="Predicted breed name")
12
+ confidence: float = Field(..., ge=0, le=1, description="Confidence score")
13
+
14
+
15
+ class BreedInfo(BaseModel):
16
+ """Breed metadata for the predicted breed."""
17
+ breed_name: str
18
+ animal_type: str
19
+ region: str
20
+ avg_milk_liters_per_day: str
21
+ lifespan_years: str
22
+ primary_use: str
23
+ description: str
24
+
25
+
26
+ class PredictResponse(BaseModel):
27
+ """Full prediction response."""
28
+ predicted_breed: str
29
+ confidence: float = Field(..., ge=0, le=1)
30
+ top_k: list[PredictionItem]
31
+ breed_info: Optional[BreedInfo] = None
32
+ model_version: str = "v1.0"
33
+ inference_time_ms: float
34
+ warning: Optional[str] = None
35
+
36
+
37
+ class PredictURLRequest(BaseModel):
38
+ """Request body for URL-based prediction."""
39
+ url: str = Field(..., description="URL of the image to classify")
40
+ top_k: int = Field(default=3, ge=1, le=10, description="Number of top predictions")
41
+
42
+
43
+ class PredictBase64Request(BaseModel):
44
+ """Request body for base64-based prediction."""
45
+ image: str = Field(..., description="Base64-encoded image string")
46
+ top_k: int = Field(default=3, ge=1, le=10, description="Number of top predictions")
47
+
48
+
49
+ class ErrorResponse(BaseModel):
50
+ """Error response."""
51
+ detail: str
52
+ error_type: str = "prediction_error"
backend/app/services/__init__.py ADDED
File without changes
backend/app/services/breed_info.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Breed information service.
3
+ Provides breed metadata lookup from CSV.
4
+ """
5
+
6
+ import csv
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from backend.app.core.config import get_settings
11
+ from backend.app.core.logging import logger
12
+
13
+
14
+ class BreedInfoService:
15
+ """Service for breed metadata lookup."""
16
+
17
+ def __init__(self):
18
+ self._data: dict[str, dict] = {}
19
+ self._loaded = False
20
+
21
+ def load(self) -> None:
22
+ """Load breed metadata from CSV."""
23
+ if self._loaded:
24
+ return
25
+
26
+ settings = get_settings()
27
+ csv_path = Path(settings.metadata_path)
28
+
29
+ if not csv_path.exists():
30
+ logger.warning(f"Breed metadata file not found: {csv_path}")
31
+ return
32
+
33
+ try:
34
+ with open(csv_path, 'r', encoding='utf-8') as f:
35
+ reader = csv.DictReader(f)
36
+ for row in reader:
37
+ breed_name = row['breed_name'].strip()
38
+ self._data[breed_name] = {
39
+ 'breed_id': int(row['breed_id']),
40
+ 'breed_name': breed_name,
41
+ 'animal_type': row['animal_type'].strip(),
42
+ 'region': row['region'].strip(),
43
+ 'avg_milk_liters_per_day': row['avg_milk_liters_per_day'].strip(),
44
+ 'lifespan_years': row['lifespan_years'].strip(),
45
+ 'primary_use': row['primary_use'].strip(),
46
+ 'coat_color_notes': row['coat_color_notes'].strip(),
47
+ 'horn_notes': row['horn_notes'].strip(),
48
+ 'description': row['description'].strip(),
49
+ 'source_reference': row['source_reference'].strip(),
50
+ }
51
+
52
+ self._loaded = True
53
+ logger.info(f"Loaded metadata for {len(self._data)} breeds")
54
+
55
+ except Exception as e:
56
+ logger.error(f"Failed to load breed metadata: {e}")
57
+
58
+ def get_breed(self, breed_name: str) -> Optional[dict]:
59
+ """Get breed info by exact name."""
60
+ return self._data.get(breed_name)
61
+
62
+ def get_breed_summary(self, breed_name: str) -> Optional[dict]:
63
+ """Get condensed breed info for prediction responses."""
64
+ info = self._data.get(breed_name)
65
+ if info is None:
66
+ return None
67
+ return {
68
+ 'breed_name': info['breed_name'],
69
+ 'animal_type': info['animal_type'],
70
+ 'region': info['region'],
71
+ 'avg_milk_liters_per_day': info['avg_milk_liters_per_day'],
72
+ 'lifespan_years': info['lifespan_years'],
73
+ 'primary_use': info['primary_use'],
74
+ 'description': info['description'],
75
+ }
76
+
77
+ def get_all(self) -> list[dict]:
78
+ """Return all breeds."""
79
+ return list(self._data.values())
80
+
81
+ def search(self, query: str) -> list[dict]:
82
+ """Case-insensitive search."""
83
+ q = query.lower()
84
+ return [b for b in self._data.values() if q in b['breed_name'].lower()]
85
+
86
+ @property
87
+ def num_breeds(self) -> int:
88
+ return len(self._data)
89
+
90
+
91
+ # Singleton
92
+ breed_info_service = BreedInfoService()
backend/app/services/image_loader.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Image loading service.
3
+ Handles loading images from file upload, URL, and base64.
4
+ """
5
+
6
+ import base64
7
+ from io import BytesIO
8
+
9
+ import requests
10
+ from PIL import Image
11
+
12
+ from backend.app.core.config import get_settings
13
+ from backend.app.core.logging import logger
14
+
15
+
16
+ def load_image_from_upload(file_bytes: bytes) -> Image.Image:
17
+ """Load image from uploaded file bytes."""
18
+ try:
19
+ image = Image.open(BytesIO(file_bytes)).convert('RGB')
20
+ return image
21
+ except Exception as e:
22
+ logger.error(f"Failed to load uploaded image: {e}")
23
+ raise ValueError(f"Invalid image file: {e}")
24
+
25
+
26
+ def load_image_from_url(url: str) -> Image.Image:
27
+ """Load image from a URL."""
28
+ settings = get_settings()
29
+ try:
30
+ response = requests.get(url, timeout=settings.url_timeout, stream=True)
31
+ response.raise_for_status()
32
+
33
+ # Check content length
34
+ content_length = response.headers.get('Content-Length')
35
+ if content_length and int(content_length) > settings.max_image_size_mb * 1024 * 1024:
36
+ raise ValueError(f"Image too large (max {settings.max_image_size_mb}MB)")
37
+
38
+ image = Image.open(BytesIO(response.content)).convert('RGB')
39
+ return image
40
+ except requests.RequestException as e:
41
+ logger.error(f"Failed to fetch image from URL: {e}")
42
+ raise ValueError(f"Could not fetch image from URL: {e}")
43
+ except Exception as e:
44
+ logger.error(f"Failed to load image from URL: {e}")
45
+ raise ValueError(f"Invalid image from URL: {e}")
46
+
47
+
48
+ def load_image_from_base64(b64_string: str) -> Image.Image:
49
+ """Load image from base64-encoded string."""
50
+ try:
51
+ # Remove data URI prefix if present
52
+ if ',' in b64_string:
53
+ b64_string = b64_string.split(',', 1)[1]
54
+
55
+ image_bytes = base64.b64decode(b64_string)
56
+ image = Image.open(BytesIO(image_bytes)).convert('RGB')
57
+ return image
58
+ except Exception as e:
59
+ logger.error(f"Failed to load base64 image: {e}")
60
+ raise ValueError(f"Invalid base64 image: {e}")
backend/app/services/inference.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Inference service.
3
+ Loads the model on startup and provides prediction functionality.
4
+ """
5
+
6
+ import sys
7
+ import time
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import torch
12
+ import torch.nn.functional as F
13
+ from PIL import Image
14
+
15
+ from backend.app.core.config import get_settings
16
+ from backend.app.core.logging import logger
17
+
18
+
19
+ class InferenceService:
20
+ """Singleton service for model inference."""
21
+
22
+ def __init__(self):
23
+ self.model = None
24
+ self.class_names: list[str] = []
25
+ self.device = torch.device('cpu')
26
+ self.settings = get_settings()
27
+ self._loaded = False
28
+
29
+ def load(self) -> None:
30
+ """Load model and class names on startup."""
31
+ if self._loaded:
32
+ return
33
+
34
+ logger.info("Loading model...")
35
+ start = time.time()
36
+
37
+ # Detect device
38
+ if torch.cuda.is_available():
39
+ self.device = torch.device('cuda')
40
+ elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
41
+ self.device = torch.device('mps')
42
+
43
+ # Load class names
44
+ classes_path = Path(self.settings.classes_path)
45
+ if classes_path.exists():
46
+ with open(classes_path, 'r') as f:
47
+ self.class_names = [c.strip() for c in f.read().split(',') if c.strip()]
48
+
49
+ # Load model
50
+ model_path = Path(self.settings.model_path)
51
+ if not model_path.exists():
52
+ logger.warning(f"Model file not found: {model_path}")
53
+ logger.info("Service will start without a model. Upload a model to enable predictions.")
54
+ return
55
+
56
+ try:
57
+ checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
58
+
59
+ if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
60
+ # State dict checkpoint from new training pipeline
61
+ model_name = checkpoint.get('model_name', self.settings.model_name)
62
+ if 'class_names' in checkpoint:
63
+ self.class_names = checkpoint['class_names']
64
+ self.model = self._create_model(model_name, len(self.class_names))
65
+ self.model.load_state_dict(checkpoint['model_state_dict'])
66
+ elif isinstance(checkpoint, torch.nn.Module):
67
+ # Full model (legacy format)
68
+ self.model = checkpoint
69
+ else:
70
+ # Try loading as full model directly
71
+ self.model = checkpoint
72
+
73
+ if isinstance(self.model, torch.nn.Module):
74
+ self.model = self.model.to(self.device)
75
+ self.model.eval()
76
+
77
+ self._loaded = True
78
+ elapsed = time.time() - start
79
+ logger.info(f"Model loaded in {elapsed:.2f}s on {self.device} ({len(self.class_names)} classes)")
80
+
81
+ except Exception as e:
82
+ logger.error(f"Failed to load model: {e}")
83
+ raise
84
+
85
+ def _create_model(self, model_name: str, num_classes: int) -> torch.nn.Module:
86
+ """Create model architecture by name."""
87
+ # Add project root to path for ML imports
88
+ project_root = Path(__file__).resolve().parents[3]
89
+ if str(project_root) not in sys.path:
90
+ sys.path.insert(0, str(project_root))
91
+
92
+ if model_name in ('resnet', 'resnet50'):
93
+ from ml.src.models.resnet import CattleResNet
94
+ return CattleResNet(num_classes=num_classes, pretrained=False, freeze_backbone=False)
95
+ elif model_name == 'cnn':
96
+ from ml.src.models.cnn import CattleCNN
97
+ return CattleCNN(num_classes=num_classes)
98
+ elif model_name == 'mlp':
99
+ from ml.src.models.mlp import CattleMLP
100
+ return CattleMLP(num_classes=num_classes)
101
+ elif model_name in ('vit', 'vit_base'):
102
+ from ml.src.models.vit import CattleViT
103
+ return CattleViT(num_classes=num_classes, pretrained=False, freeze_backbone=False)
104
+ else:
105
+ raise ValueError(f"Unknown model type: {model_name}")
106
+
107
+ @torch.no_grad()
108
+ def predict(self, image: Image.Image, top_k: int = 3) -> dict:
109
+ """
110
+ Run prediction on a PIL Image.
111
+ Returns prediction dict.
112
+ """
113
+ if self.model is None:
114
+ # Return a mock prediction if model is missing to prevent UI crash
115
+ import random
116
+
117
+ # Use real class names if available, otherwise fallback
118
+ classes = self.class_names if self.class_names else [
119
+ "Gir", "Sahiwal", "Kankrej", "Tharparkar", "Red Sindhi"
120
+ ]
121
+
122
+ mock_breed = random.choice(classes)
123
+ other_breeds = [c for c in classes if c != mock_breed]
124
+
125
+ mock_top_k = [{'breed': mock_breed, 'confidence': 0.875}]
126
+ if other_breeds:
127
+ mock_top_k.append({'breed': other_breeds[0], 'confidence': 0.082})
128
+ if len(other_breeds) > 1:
129
+ mock_top_k.append({'breed': other_breeds[1], 'confidence': 0.043})
130
+
131
+ return {
132
+ 'predicted_breed': mock_breed,
133
+ 'confidence': 0.875,
134
+ 'top_k': mock_top_k,
135
+ 'model_version': 'mock-v1.0 (No Model Loaded)',
136
+ 'inference_time_ms': 12.5,
137
+ 'warning': '⚠️ PyTorch model file missing from server. Displaying mock AI prediction.'
138
+ }
139
+
140
+ from torchvision import transforms
141
+ start = time.time()
142
+
143
+ # Preprocess
144
+ transform = transforms.Compose([
145
+ transforms.Resize((self.settings.image_size, self.settings.image_size)),
146
+ transforms.ToTensor(),
147
+ transforms.Normalize(
148
+ mean=[0.485, 0.456, 0.406],
149
+ std=[0.229, 0.224, 0.225],
150
+ ),
151
+ ])
152
+
153
+ tensor = transform(image).unsqueeze(0).to(self.device)
154
+
155
+ # Inference
156
+ outputs = self.model(tensor)
157
+ probabilities = F.softmax(outputs, dim=1).squeeze()
158
+
159
+ # Top-k
160
+ top_k_val = min(top_k, len(self.class_names))
161
+ top_probs, top_indices = torch.topk(probabilities, top_k_val)
162
+
163
+ top_k_results = []
164
+ for prob, idx in zip(top_probs, top_indices):
165
+ breed_name = self.class_names[idx.item()] if idx.item() < len(self.class_names) else f"class_{idx.item()}"
166
+ top_k_results.append({
167
+ 'breed': breed_name,
168
+ 'confidence': round(prob.item(), 4),
169
+ })
170
+
171
+ best = top_k_results[0]
172
+ elapsed_ms = (time.time() - start) * 1000
173
+
174
+ result = {
175
+ 'predicted_breed': best['breed'],
176
+ 'confidence': best['confidence'],
177
+ 'top_k': top_k_results,
178
+ 'model_version': self.settings.model_version,
179
+ 'inference_time_ms': round(elapsed_ms, 2),
180
+ }
181
+
182
+ # Confidence warning
183
+ if best['confidence'] < 0.5:
184
+ result['warning'] = 'Prediction confidence is low. Use a clear side-view image for better results.'
185
+ elif best['confidence'] < 0.75:
186
+ result['warning'] = 'Prediction confidence is moderate. Use a clear side-view image for better results.'
187
+
188
+ return result
189
+
190
+ @property
191
+ def is_loaded(self) -> bool:
192
+ return self._loaded and self.model is not None
193
+
194
+
195
+ # Singleton
196
+ inference_service = InferenceService()
backend/requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.104.0
2
+ uvicorn>=0.24.0
3
+ pydantic>=2.5.0
4
+ pydantic-settings>=2.1.0
5
+ python-multipart>=0.0.6
6
+ torch>=2.1.0
7
+ torchvision>=0.16.0
8
+ Pillow>=10.0.0
9
+ requests>=2.31.0
10
+ numpy>=1.24.0
11
+ PyYAML>=6.0.1
12
+ gunicorn>=21.2.0
13
+ python-dotenv>=1.0.0
14
+ timm>=0.9.0
colab_run_all.ipynb ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# \ud83d\udc04 Cattle Breed Classifier \u2014 Colab Runner\n",
8
+ "\n",
9
+ "This notebook sets up the environment on **Google Colab** and runs all training notebooks sequentially.\n",
10
+ "\n",
11
+ "### What this does\n",
12
+ "1. Verifies GPU availability\n",
13
+ "2. Clones the repo (dataset included)\n",
14
+ "3. Installs missing dependencies\n",
15
+ "4. Runs each notebook in order:\n",
16
+ " - `00_data_audit` \u2192 `01_mlp_baseline` \u2192 `02_cnn_from_scratch` \u2192 `03_resnet_transfer_learning` \u2192 `04_vit_transfer_learning` \u2192 `05_model_comparison`\n",
17
+ "\n",
18
+ "> \u26a0\ufe0f **Runtime**: Select **GPU** runtime before running: *Runtime \u2192 Change runtime type \u2192 T4 GPU*"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "markdown",
23
+ "metadata": {},
24
+ "source": [
25
+ "## 0. Check GPU"
26
+ ]
27
+ },
28
+ {
29
+ "cell_type": "code",
30
+ "execution_count": null,
31
+ "metadata": {},
32
+ "outputs": [],
33
+ "source": [
34
+ "import torch\n",
35
+ "print(f'PyTorch version: {torch.__version__}')\n",
36
+ "print(f'CUDA available: {torch.cuda.is_available()}')\n",
37
+ "if torch.cuda.is_available():\n",
38
+ " print(f'GPU: {torch.cuda.get_device_name(0)}')\n",
39
+ " print(f'Memory: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB')\n",
40
+ "else:\n",
41
+ " print('\u26a0\ufe0f No GPU detected. Go to Runtime \u2192 Change runtime type \u2192 T4 GPU')"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "markdown",
46
+ "metadata": {},
47
+ "source": [
48
+ "## 1. Clone Repository"
49
+ ]
50
+ },
51
+ {
52
+ "cell_type": "code",
53
+ "execution_count": null,
54
+ "metadata": {},
55
+ "outputs": [],
56
+ "source": [
57
+ "import os\n",
58
+ "\n",
59
+ "REPO_URL = 'https://github.com/ajitsingh98/cattle-breed-classifier-webapp.git'\n",
60
+ "REPO_DIR = '/content/cattle-breed-classifier-webapp'\n",
61
+ "\n",
62
+ "if not os.path.exists(REPO_DIR):\n",
63
+ " !git clone {REPO_URL} {REPO_DIR}\n",
64
+ "else:\n",
65
+ " print(f'Repo already cloned at {REPO_DIR}')\n",
66
+ " !cd {REPO_DIR} && git pull\n",
67
+ "\n",
68
+ "os.chdir(REPO_DIR)\n",
69
+ "print(f'\\nWorking directory: {os.getcwd()}')"
70
+ ]
71
+ },
72
+ {
73
+ "cell_type": "markdown",
74
+ "metadata": {},
75
+ "source": [
76
+ "## 2. Install Dependencies"
77
+ ]
78
+ },
79
+ {
80
+ "cell_type": "code",
81
+ "execution_count": null,
82
+ "metadata": {},
83
+ "outputs": [],
84
+ "source": [
85
+ "# Colab already has PyTorch, torchvision, numpy, pandas, matplotlib, seaborn, PIL, scikit-learn.\n",
86
+ "# Install only the missing packages.\n",
87
+ "!pip install -q PyYAML gdown aiofiles aiohttp nest_asyncio"
88
+ ]
89
+ },
90
+ {
91
+ "cell_type": "markdown",
92
+ "metadata": {},
93
+ "source": [
94
+ "## 3. Set Up Python Path"
95
+ ]
96
+ },
97
+ {
98
+ "cell_type": "code",
99
+ "execution_count": null,
100
+ "metadata": {},
101
+ "outputs": [],
102
+ "source": [
103
+ "import sys\n",
104
+ "from pathlib import Path\n",
105
+ "\n",
106
+ "PROJECT_ROOT = Path(REPO_DIR).resolve()\n",
107
+ "if str(PROJECT_ROOT) not in sys.path:\n",
108
+ " sys.path.insert(0, str(PROJECT_ROOT))\n",
109
+ "\n",
110
+ "# Create artifact directories\n",
111
+ "for d in ['ml/artifacts/manifests', 'ml/artifacts/checkpoints',\n",
112
+ " 'ml/artifacts/figures', 'ml/artifacts/logs', 'ml/artifacts/reports']:\n",
113
+ " (PROJECT_ROOT / d).mkdir(parents=True, exist_ok=True)\n",
114
+ "\n",
115
+ "# Verify dataset exists\n",
116
+ "data_dir = PROJECT_ROOT / 'Cattle_Resized'\n",
117
+ "class_dirs = sorted([d for d in data_dir.iterdir() if d.is_dir()])\n",
118
+ "total_images = sum(len(list(d.glob('*'))) for d in class_dirs)\n",
119
+ "print(f'Dataset: {len(class_dirs)} breeds, {total_images} images')\n",
120
+ "print(f'Project root: {PROJECT_ROOT}')"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "markdown",
125
+ "metadata": {},
126
+ "source": [
127
+ "## 4. Run All Notebooks\n",
128
+ "\n",
129
+ "Each notebook is executed in order using `nbconvert`. Output is displayed inline.\n",
130
+ "\n",
131
+ "You can also **skip this cell** and open each notebook individually from the file browser on the left:\n",
132
+ "> `cattle-breed-classifier-webapp/ml/notebooks/`"
133
+ ]
134
+ },
135
+ {
136
+ "cell_type": "code",
137
+ "execution_count": null,
138
+ "metadata": {},
139
+ "outputs": [],
140
+ "source": [
141
+ "!pip install -q papermill jupyter ipykernel"
142
+ ]
143
+ },
144
+ {
145
+ "cell_type": "code",
146
+ "execution_count": null,
147
+ "metadata": {},
148
+ "outputs": [],
149
+ "source": [
150
+ "import subprocess, time\n",
151
+ "\n",
152
+ "NOTEBOOKS = [\n",
153
+ " '00_data_audit.ipynb',\n",
154
+ " '01_mlp_baseline.ipynb',\n",
155
+ " '02_cnn_from_scratch.ipynb',\n",
156
+ " '03_resnet_transfer_learning.ipynb',\n",
157
+ " '04_vit_transfer_learning.ipynb',\n",
158
+ " '05_model_comparison.ipynb',\n",
159
+ "]\n",
160
+ "\n",
161
+ "NOTEBOOK_DIR = PROJECT_ROOT / 'ml' / 'notebooks'\n",
162
+ "results = {}\n",
163
+ "\n",
164
+ "for nb_name in NOTEBOOKS:\n",
165
+ " nb_path = NOTEBOOK_DIR / nb_name\n",
166
+ " print(f'\\n{\"=\" * 60}')\n",
167
+ " print(f'\u25b6 Running: {nb_name}')\n",
168
+ " print(f'{\"=\" * 60}')\n",
169
+ "\n",
170
+ " start = time.time()\n",
171
+ " # We use papermill because it supports streaming cell outputs (--log-output)\n",
172
+ " result = subprocess.run(\n",
173
+ " [\n",
174
+ " \"papermill\",\n",
175
+ " str(nb_path),\n",
176
+ " str(nb_path), # overwrite inplace so output is saved in the notebook\n",
177
+ " \"--log-output\",\n",
178
+ " \"--kernel\", \"python3\"\n",
179
+ " ],\n",
180
+ " cwd=str(PROJECT_ROOT),\n",
181
+ " env={**os.environ, \"PYTHONPATH\": str(PROJECT_ROOT)},\n",
182
+ " )\n",
183
+ " elapsed = time.time() - start\n",
184
+ "\n",
185
+ " if result.returncode == 0:\n",
186
+ " status = '\u2705 PASSED'\n",
187
+ " else:\n",
188
+ " status = '\u274c FAILED'\n",
189
+ "\n",
190
+ " results[nb_name] = {'status': status, 'time': elapsed}\n",
191
+ " print(f'{status} ({elapsed:.1f}s)')\n",
192
+ "\n",
193
+ "# Summary\n",
194
+ "print(f'\\n{\"=\" * 60}')\n",
195
+ "print('Summary')\n",
196
+ "print(f'{\"=\" * 60}')\n",
197
+ "for nb, info in results.items():\n",
198
+ " print(f\" {info['status']} {nb:45s} {info['time']:6.1f}s\")\n",
199
+ "total_time = sum(r['time'] for r in results.values())\n",
200
+ "print(f'\\nTotal time: {total_time/60:.1f} minutes')"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "markdown",
205
+ "metadata": {},
206
+ "source": [
207
+ "## 5. Download Artifacts (Optional)\n",
208
+ "\n",
209
+ "After training completes, download the best model checkpoint and reports."
210
+ ]
211
+ },
212
+ {
213
+ "cell_type": "code",
214
+ "execution_count": null,
215
+ "metadata": {},
216
+ "outputs": [],
217
+ "source": [
218
+ "# List generated artifacts\n",
219
+ "import glob\n",
220
+ "\n",
221
+ "print('=== Checkpoints ===')\n",
222
+ "for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/checkpoints/*.pth'))):\n",
223
+ " size_mb = os.path.getsize(f) / 1024 / 1024\n",
224
+ " print(f' {Path(f).name:40s} {size_mb:8.1f} MB')\n",
225
+ "\n",
226
+ "print('\\n=== Figures ===')\n",
227
+ "for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/figures/*.png'))):\n",
228
+ " print(f' {Path(f).name}')\n",
229
+ "\n",
230
+ "print('\\n=== Reports ===')\n",
231
+ "for f in sorted(glob.glob(str(PROJECT_ROOT / 'ml/artifacts/reports/*.json'))):\n",
232
+ " print(f' {Path(f).name}')"
233
+ ]
234
+ },
235
+ {
236
+ "cell_type": "code",
237
+ "execution_count": null,
238
+ "metadata": {},
239
+ "outputs": [],
240
+ "source": [
241
+ "# Zip and download artifacts\n",
242
+ "!cd {REPO_DIR} && zip -r /content/artifacts.zip ml/artifacts/\n",
243
+ "\n",
244
+ "from google.colab import files\n",
245
+ "files.download('/content/artifacts.zip')\n",
246
+ "print('\\n\u2705 Download started! Check your browser downloads.')"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "markdown",
251
+ "metadata": {},
252
+ "source": [
253
+ "---\n",
254
+ "\n",
255
+ "### \ud83d\udca1 Running Notebooks Individually\n",
256
+ "\n",
257
+ "Instead of the automated runner above, you can open each notebook directly:\n",
258
+ "\n",
259
+ "1. In the **Colab file browser** (left panel), navigate to: \n",
260
+ " `cattle-breed-classifier-webapp/ml/notebooks/`\n",
261
+ "2. Double-click any `.ipynb` file to open it in a new tab\n",
262
+ "3. Run cells with `Shift+Enter`\n",
263
+ "\n",
264
+ "**Important**: Each notebook auto-detects the project root, so they work both from the automated runner and when opened individually."
265
+ ]
266
+ }
267
+ ],
268
+ "metadata": {
269
+ "kernelspec": {
270
+ "display_name": "Python 3",
271
+ "language": "python",
272
+ "name": "python3"
273
+ },
274
+ "language_info": {
275
+ "name": "python",
276
+ "version": "3.10.0"
277
+ },
278
+ "colab": {
279
+ "provenance": [],
280
+ "gpuType": "T4"
281
+ },
282
+ "accelerator": "GPU"
283
+ },
284
+ "nbformat": 4,
285
+ "nbformat_minor": 4
286
+ }
data/breed_metadata.csv ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ breed_id,breed_name,animal_type,region,avg_milk_liters_per_day,lifespan_years,primary_use,coat_color_notes,horn_notes,description,source_reference
2
+ 1,Alambadi Cow,Cow,"Tamil Nadu, India",2-3,15-18,Draught,Grey to dark grey with white markings,Long curved horns spreading outward,Hardy draught breed from Tamil Nadu adapted to dry semi-arid conditions. Known for endurance and strength in agricultural work.,NBAGR India
3
+ 2,Amritmahal Cow,Cow,"Karnataka, India",1-2,18-20,Draught,Grey to white with darker head and neck,Long lyre-shaped upward curving horns,Elite draught breed from Karnataka known for speed and endurance. Originally bred for the Mysore army for hauling equipment.,NBAGR India
4
+ 3,Banni Buffalo,Buffalo,"Gujarat, India",10-14,20-25,Dairy,Jet black with occasional white markings on forehead,Medium curved sickle-shaped horns,High-yielding buffalo breed from the Banni grasslands of Kutch. Well adapted to arid conditions with excellent milk fat content.,NBAGR India
5
+ 4,Bargur Cow,Cow,"Tamil Nadu, India",2-3,15-18,Draught,Brown to reddish brown with white patches,Medium curved horns,Agile draught breed from the Bargur hills of Erode district. Known for its speed and hill-terrain adaptability.,NBAGR India
6
+ 5,Dangi Cow,Cow,"Maharashtra, India",1-3,15-18,Dual Purpose,White with red or black spots,Short stumpy horns,Hardy breed from the hilly Dangs region. Tolerant to heavy rainfall and humid conditions. Used for both draught and limited milk production.,NBAGR India
7
+ 6,Deoni Cow,Cow,"Maharashtra / Karnataka, India",3-5,15-18,Dual Purpose,White body with black or red spots on head and neck,Medium sized curved horns,"Dual-purpose breed from the Deccan plateau. Decent milk yield and good draught capability. Known as ""Dongari"" or ""Dongerpati"" cattle.",NBAGR India
8
+ 7,Gir Cow,Cow,"Gujarat, India",6-10,12-15,Dairy,Reddish to spotted red-white with distinctive curved forehead,Long pendulous ears; curved horns,One of the principal dairy breeds of India. Highly heat tolerant with excellent disease resistance. Exported worldwide to tropical countries.,NBAGR India
9
+ 8,Hallikar Cow,Cow,"Karnataka, India",1-2,18-20,Draught,Grey to dark grey,Long lyre-shaped horns curving backward,Premier draught breed of South India known for compact muscular build. Progenitor of several other South Indian breeds.,NBAGR India
10
+ 9,Jaffrabadi Buffalo,Buffalo,"Gujarat, India",8-12,20-25,Dairy,Black,Heavy massive horns curving downward then upward,Heaviest Indian buffalo breed from Gir forests of Gujarat. Known for high milk yield with excellent fat percentage.,NBAGR India
11
+ 10,Kangayam Cow,Cow,"Tamil Nadu, India",2-4,18-20,Draught,Grey to white with red or brown tinge,Short sturdy horns,Powerful draught breed from Tamil Nadu. Known for compact muscular body and endurance in heavy agricultural work.,NBAGR India
12
+ 11,Kankrej Cow,Cow,"Gujarat / Rajasthan, India",5-8,15-18,Dual Purpose,Silver-grey to steel grey with darker forequarters,Strong lyre-shaped horns,Large dual-purpose breed. Known for heavy build and fast trotting gait. Popular for both milk production and draught work.,NBAGR India
13
+ 12,Kasaragod Cow,Cow,"Kerala, India",2-3,15-18,Dwarf Cattle,Reddish brown to black,Short small horns,Small-sized cattle breed from the Kasaragod district of Kerala. Well adapted to the tropical coastal climate and resistant to local diseases.,NBAGR India
14
+ 13,Kenkatha Cow,Cow,"Uttar Pradesh / Madhya Pradesh, India",2-4,15-18,Draught,Grey to dark grey with lighter underbelly,Short thick horns,Compact draught breed from the Ken river valley of Bundelkhand. Hardy and well-suited to rocky terrain.,NBAGR India
15
+ 14,Kherigarh Cow,Cow,"Uttar Pradesh, India",3-5,15-18,Dual Purpose,White to light grey,Medium lyre-shaped horns,Medium-sized dual-purpose breed from the Kheri district of UP. Known for reasonable milk yield and good draught capability.,NBAGR India
16
+ 15,Malnad gidda Cow,Cow,"Karnataka, India",1-3,18-20,Dairy (small-scale),Varied: black brown red or white,Short small horns or polled,Smallest Indian cattle breed from the Western Ghats of Karnataka. Extremely hardy with strong disease resistance adapted to heavy rainfall and hilly terrain.,NBAGR India
17
+ 16,Mehsana Buffalo,Buffalo,"Gujarat, India",8-12,20-25,Dairy,Black to brownish black,Sickle-shaped curving upward and backward,Important dairy buffalo from North Gujarat. Cross between Murrah and Surti. Consistent milk producer with good fat content.,NBAGR India
18
+ 17,Nagori Cow,Cow,"Rajasthan, India",2-4,15-18,Draught,White to light grey with darker extremities,Medium upward-pointing horns,Tall well-built draught breed from the Nagaur district of Rajasthan. Prized for fast trotting ability and endurance in desert conditions.,NBAGR India
19
+ 18,Nagpuri Buffalo,Buffalo,"Maharashtra, India",5-7,20-25,Dual Purpose,Black,Very long flat curved sickle-shaped horns (can exceed 60cm),Distinctive buffalo breed from the Nagpur region. Known for extremely long horns and adaptation to hot dry climate of Vidarbha.,NBAGR India
20
+ 19,Nili ravi Buffalo,Buffalo,"Punjab, India / Pakistan",8-14,20-25,Dairy,Black with white markings on forehead and legs,Small tightly curled horns,Premier dairy buffalo breed from the Punjab region. One of the highest milk-yielding buffalo breeds in the world.,NBAGR India
21
+ 20,Nimari Cow,Cow,"Madhya Pradesh, India",2-4,15-18,Dual Purpose,Red to reddish brown with darker extremities,Medium lyre-shaped horns,"Medium-sized dual-purpose breed from the Nimar valley (Narmada basin). Suitable for both draught and moderate milk production.",NBAGR India
22
+ 21,Pulikulam Cow,Cow,"Tamil Nadu, India",1-2,15-18,Draught / Sport,Grey to dark grey,Sharp curved horns,"Small agile breed from the Madurai-Sivaganga region. Traditionally used in the Jallikattu bull-taming sport. Known for agility and tenacity.",NBAGR India
23
+ 22,Rathi Cow,Cow,"Rajasthan, India",6-8,15-18,Dairy,Brown with white patches,Short to medium curved horns,"One of the best dairy breeds of Rajasthan from the Bikaner region. Adapted to arid conditions with efficient feed conversion.",NBAGR India
24
+ 23,Sahiwal Cow,Cow,"Punjab, India / Pakistan",8-12,15-18,Dairy,Reddish to reddish brown with occasional white patches,Short stumpy horns (often polled),"One of the best dairy breeds of the Indian subcontinent. Highly heat-tolerant and tick-resistant. Exported globally to tropical countries.",NBAGR India
25
+ 24,Shurti Buffalo,Buffalo,"Karnataka, India",4-6,20-25,Dairy,Black to brownish black,Medium flat horns curving backward,"Small to medium dairy buffalo from the North Karnataka region. Adapted to medium rainfall zone and maintained by small farmers.",NBAGR India
26
+ 25,Tharparkar Cow,Cow,"Rajasthan, India / Sindh, Pakistan",6-10,18-20,Dairy,White to grey,Medium lyre-shaped horns curving upward,"Hardy dual-purpose breed from the Thar desert. Among the best milkers of Indian zebu breeds. Well adapted to extreme heat and scarce water.",NBAGR India
27
+ 26,Umblachery Cow,Cow,"Tamil Nadu, India",1-2,15-18,Draught,Reddish brown calves turning grey as adults,Short curved horns,"Compact draught breed from the Cauvery delta region. Known for ability to work in waterlogged paddy fields. Important for rice cultivation.",NBAGR India
deploy.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ try:
6
+ from huggingface_hub import HfApi, create_repo
7
+ except ImportError:
8
+ print("Error: huggingface_hub not found. Run: .venv/bin/pip install huggingface_hub")
9
+ sys.exit(1)
10
+
11
+ def deploy():
12
+ print("🚀 Cattle Breed Classifier Hugging Face Deployer")
13
+ print("-" * 50)
14
+
15
+ # 1. Get credentials securely
16
+ username = input("Enter your Hugging Face Username: ").strip()
17
+ space_name = input("Enter your desired Space Name (e.g. cattle-classifier): ").strip()
18
+ token = input("Enter your Hugging Face Write Token: ").strip()
19
+
20
+ if not username or not space_name or not token:
21
+ print("Error: All fields are required.")
22
+ sys.exit(1)
23
+
24
+ repo_id = f"{username}/{space_name}"
25
+ api = HfApi(token=token)
26
+
27
+ print(f"\n[1/3] Creating Space '{repo_id}' on Hugging Face...")
28
+ try:
29
+ create_repo(
30
+ repo_id=repo_id,
31
+ repo_type="space",
32
+ space_sdk="docker",
33
+ token=token,
34
+ exist_ok=True
35
+ )
36
+ print("✅ Space created/verified successfully!")
37
+ except Exception as e:
38
+ print(f"❌ Failed to create space: {e}")
39
+ sys.exit(1)
40
+
41
+ print("\n[2/3] Uploading Application & Massive ViT Model...")
42
+ print("⏳ This may take several minutes depending on your internet upload speed (Model is ~460MB). Please wait...")
43
+
44
+ # Files to ignore (don't upload heavy training folders or virtual environments)
45
+ ignore_patterns = [
46
+ ".venv/*",
47
+ ".git/*",
48
+ "__pycache__/*",
49
+ "*.pyc",
50
+ "Cattle_Resized/*",
51
+ "ml/artifacts/checkpoints/cnn_best.pth",
52
+ "ml/artifacts/checkpoints/mlp_best.pth",
53
+ "ml/artifacts/checkpoints/resnet_best.pth",
54
+ # Ignore models dir except vit_best.pth and classes.txt
55
+ "frontend/node_modules/*"
56
+ ]
57
+
58
+ try:
59
+ api.upload_folder(
60
+ folder_path=".",
61
+ repo_id=repo_id,
62
+ repo_type="space",
63
+ ignore_patterns=ignore_patterns,
64
+ commit_message="Initial Deployment: Best ViT Model"
65
+ )
66
+ print("✅ Upload complete!")
67
+ except Exception as e:
68
+ print(f"❌ Failed to upload files: {e}")
69
+ sys.exit(1)
70
+
71
+ print(f"\n[3/3] Deployment Successful! 🎉")
72
+ print(f"Your application is now building in the cloud.")
73
+ print(f"View it live here: https://huggingface.co/spaces/{username}/{space_name}")
74
+
75
+ if __name__ == "__main__":
76
+ deploy()
docker-compose.yml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker Compose for Cattle Breed Classifier
2
+ # Run: docker-compose up --build
3
+
4
+ version: "3.9"
5
+
6
+ services:
7
+ backend:
8
+ build:
9
+ context: .
10
+ dockerfile: backend/Dockerfile
11
+ container_name: cattle-api
12
+ ports:
13
+ - "8000:8000"
14
+ environment:
15
+ - MODEL_PATH=/app/models/vit_best.pth
16
+ - METADATA_PATH=/app/data/breed_metadata.csv
17
+ - CLASSES_PATH=/app/models/classes.txt
18
+ - MODEL_NAME=vit
19
+ - MODEL_VERSION=v2.0
20
+ volumes:
21
+ - ./models:/app/models
22
+ - ./data:/app/data
23
+ restart: unless-stopped
24
+ healthcheck:
25
+ test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
26
+ interval: 30s
27
+ timeout: 10s
28
+ retries: 3
29
+
30
+ frontend:
31
+ build:
32
+ context: ./frontend
33
+ dockerfile: Dockerfile
34
+ container_name: cattle-frontend
35
+ ports:
36
+ - "3000:80"
37
+ depends_on:
38
+ - backend
39
+ restart: unless-stopped
frontend/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
frontend/Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend Dockerfile
2
+ FROM node:20-alpine AS build
3
+
4
+ WORKDIR /app
5
+ COPY package*.json ./
6
+ RUN npm ci
7
+ COPY . .
8
+ RUN npm run build
9
+
10
+ # Serve with nginx
11
+ FROM nginx:alpine
12
+ COPY --from=build /app/dist /usr/share/nginx/html
13
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
14
+ EXPOSE 80
15
+ CMD ["nginx", "-g", "daemon off;"]
frontend/README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
frontend/dist/assets/index-CWZymrby.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap";:root{--color-bg-primary:#1f2330;--color-bg-secondary:#2c3144;--color-bg-card:#373e56;--color-bg-card-hover:#434b68;--color-bg-glass:#767f9e4d;--color-bg-input:#2c3144;--color-accent:#daa464;--color-accent-light:#dec384;--color-accent-dark:#b6854d;--color-accent-glow:#daa46433;--color-success:#10b981;--color-error:#ef4444;--color-warning:#daa464;--color-info:#767f9e;--color-text-primary:#e8ddb4;--color-text-secondary:#dec384;--color-text-muted:#767f9e;--color-border:#767f9e4d;--color-border-accent:#daa46466;--gradient-hero:linear-gradient(135deg, #1f2330 0%, #373e56 50%, #2c3144 100%);--gradient-accent:linear-gradient(135deg, #dec384 0%, #daa464 100%);--gradient-card:linear-gradient(145deg, #373e56e6, #2c3144b3);--shadow-sm:0 1px 3px #0000004d;--shadow-md:0 4px 14px #0006;--shadow-lg:0 10px 40px #00000080;--shadow-glow:0 0 30px #daa46426;--radius-sm:8px;--radius-md:12px;--radius-lg:16px;--radius-xl:24px;--font-heading:"Outfit", sans-serif;--font-body:"Inter", sans-serif;--transition-fast:.15s ease;--transition-normal:.25s ease;--transition-slow:.4s cubic-bezier(.4, 0, .2, 1)}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;font-size:16px}body{font-family:var(--font-body);background:var(--color-bg-primary);color:var(--color-text-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-height:100vh;line-height:1.6}#root{flex-direction:column;min-height:100vh;display:flex}h1,h2,h3,h4,h5,h6{font-family:var(--font-heading);font-weight:700;line-height:1.2}a{color:var(--color-accent);transition:color var(--transition-fast);text-decoration:none}a:hover{color:var(--color-accent-light)}.navbar{z-index:100;-webkit-backdrop-filter:blur(16px)saturate(180%);backdrop-filter:blur(16px)saturate(180%);border-bottom:1px solid var(--color-border);background:#0a0e17d9;padding:0 2rem;position:sticky;top:0}.navbar-inner{justify-content:space-between;align-items:center;max-width:1200px;height:64px;margin:0 auto;display:flex}.navbar-brand{font-family:var(--font-heading);color:var(--color-text-primary);align-items:center;gap:10px;font-size:1.25rem;font-weight:700;display:flex}.navbar-brand span{background:var(--gradient-accent);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.nav-links{gap:6px;list-style:none;display:flex}.nav-links a{border-radius:var(--radius-sm);color:var(--color-text-secondary);transition:all var(--transition-fast);padding:8px 16px;font-size:.9rem;font-weight:500}.nav-links a:hover,.nav-links a.active{background:var(--color-accent-glow);color:var(--color-accent)}.page{flex:1;width:100%;max-width:1200px;margin:0 auto;padding:2rem}.section-title{margin-bottom:.5rem;font-size:2rem}.section-subtitle{color:var(--color-text-secondary);margin-bottom:2rem;font-size:1.05rem}.card{background:var(--gradient-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-normal);padding:1.5rem}.card:hover{border-color:var(--color-border-accent);box-shadow:var(--shadow-glow);transform:translateY(-2px)}.glass-card{background:var(--color-bg-glass);border:1px solid var(--color-border);border-radius:var(--radius-lg);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);padding:2rem}.hero{text-align:center;padding:4rem 0 3rem}.hero h1{letter-spacing:-.02em;margin-bottom:1rem;font-size:3.2rem;font-weight:800}.hero h1 .accent{background:var(--gradient-accent);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.hero p{color:var(--color-text-secondary);max-width:600px;margin:0 auto 2rem;font-size:1.15rem}.hero-cta{background:var(--gradient-accent);color:#000;border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-normal);border:none;align-items:center;gap:8px;padding:14px 32px;font-size:1rem;font-weight:600;display:inline-flex;box-shadow:0 4px 20px #f59e0b4d}.hero-cta:hover{transform:translateY(-2px);box-shadow:0 6px 30px #f59e0b66}.features-grid{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-top:3rem;display:grid}.feature-card{text-align:center;padding:2rem 1.5rem}.feature-icon{margin-bottom:1rem;font-size:2.5rem}.feature-card h3{margin-bottom:.5rem;font-size:1.1rem}.feature-card p{color:var(--color-text-secondary);font-size:.9rem}.predict-layout{grid-template-columns:1fr 1fr;gap:2rem;margin-top:1.5rem;display:grid}@media (width<=768px){.predict-layout{grid-template-columns:1fr}}.input-tabs{background:var(--color-bg-primary);border-radius:var(--radius-md);gap:4px;margin-bottom:1.5rem;padding:4px;display:flex}.input-tab{border-radius:var(--radius-sm);color:var(--color-text-secondary);cursor:pointer;transition:all var(--transition-fast);background:0 0;border:none;flex:1;padding:10px 16px;font-size:.85rem;font-weight:500}.input-tab.active{background:var(--color-accent-glow);color:var(--color-accent)}.upload-zone{border:2px dashed var(--color-border);border-radius:var(--radius-lg);text-align:center;cursor:pointer;transition:all var(--transition-normal);background:var(--color-bg-input);padding:3rem 2rem}.upload-zone:hover,.upload-zone.drag-over{border-color:var(--color-accent);background:var(--color-accent-glow)}.upload-zone .icon{margin-bottom:1rem;font-size:3rem}.upload-zone p{color:var(--color-text-secondary)}.upload-zone .hint{color:var(--color-text-muted);margin-top:.5rem;font-size:.8rem}.image-preview{border-radius:var(--radius-lg);margin-top:1rem;position:relative;overflow:hidden}.image-preview img{object-fit:contain;border-radius:var(--radius-lg);background:var(--color-bg-input);width:100%;max-height:350px}.image-preview .remove-btn{color:#fff;cursor:pointer;background:#000000b3;border:none;border-radius:50%;justify-content:center;align-items:center;width:32px;height:32px;font-size:1rem;display:flex;position:absolute;top:8px;right:8px}.url-input-group{gap:8px;margin-top:.5rem;display:flex}.url-input-group input{border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-bg-input);color:var(--color-text-primary);transition:border-color var(--transition-fast);outline:none;flex:1;padding:12px 16px;font-size:.9rem}.url-input-group input:focus{border-color:var(--color-accent)}.btn-primary{background:var(--gradient-accent);color:#000;border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-normal);border:none;padding:12px 28px;font-size:.95rem;font-weight:600}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 20px #f59e0b4d}.btn-primary:disabled{opacity:.5;cursor:not-allowed;transform:none}.btn-secondary{background:var(--color-bg-card);color:var(--color-text-primary);border:1px solid var(--color-border);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);padding:10px 20px;font-size:.9rem}.btn-secondary:hover{border-color:var(--color-accent)}.predict-btn{width:100%;margin-top:1.5rem;padding:14px;font-size:1rem}.result-card{animation:slideIn .4s var(--transition-slow)}@keyframes slideIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.result-header{justify-content:space-between;align-items:flex-start;margin-bottom:1.5rem;display:flex}.result-breed-name{font-size:1.6rem;font-weight:700;font-family:var(--font-heading)}.result-badge{border-radius:20px;align-items:center;gap:6px;padding:6px 14px;font-size:.8rem;font-weight:600;display:inline-flex}.result-badge.high{color:var(--color-success);background:#10b98126}.result-badge.medium{color:var(--color-warning);background:#f59e0b26}.result-badge.low{color:var(--color-error);background:#ef444426}.confidence-bar-container{margin-bottom:1.5rem}.confidence-bar-label{color:var(--color-text-secondary);justify-content:space-between;margin-bottom:6px;font-size:.85rem;display:flex}.confidence-bar{background:var(--color-bg-primary);border-radius:4px;height:8px;overflow:hidden}.confidence-bar-fill{background:var(--gradient-accent);border-radius:4px;height:100%;transition:width .6s cubic-bezier(.4,0,.2,1)}.topk-list{margin-bottom:1.5rem}.topk-item{border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;padding:10px 0;display:flex}.topk-item:last-child{border-bottom:none}.topk-rank{background:var(--color-bg-primary);width:28px;height:28px;color:var(--color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin-right:12px;font-size:.75rem;font-weight:600;display:flex}.topk-rank.first{background:var(--color-accent-glow);color:var(--color-accent)}.topk-name{flex:1;font-weight:500}.topk-conf{color:var(--color-text-secondary);font-size:.9rem}.breed-info-grid{grid-template-columns:repeat(2,1fr);gap:12px;display:grid}.breed-info-item{background:var(--color-bg-primary);border-radius:var(--radius-sm);padding:12px}.breed-info-label{color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;font-size:.75rem}.breed-info-value{font-size:.95rem;font-weight:600}.prediction-warning{border-radius:var(--radius-md);color:var(--color-warning);background:#f59e0b1a;border:1px solid #f59e0b33;align-items:flex-start;gap:10px;margin-top:1rem;padding:12px 16px;font-size:.85rem;display:flex}.breed-filters{flex-wrap:wrap;gap:12px;margin-bottom:2rem;display:flex}.breed-filters input,.breed-filters select{border-radius:var(--radius-md);border:1px solid var(--color-border);background:var(--color-bg-input);color:var(--color-text-primary);outline:none;padding:10px 16px;font-size:.9rem}.breed-filters input:focus,.breed-filters select:focus{border-color:var(--color-accent)}.breeds-grid{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.5rem;display:grid}.breed-card-header{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.breed-card-name{font-size:1.1rem;font-weight:600;font-family:var(--font-heading)}.breed-type-badge{text-transform:uppercase;letter-spacing:.05em;border-radius:12px;padding:4px 10px;font-size:.7rem;font-weight:600}.breed-type-badge.cow{color:var(--color-info);background:#3b82f626}.breed-type-badge.buffalo{color:#8b5cf6;background:#8b5cf626}.breed-card-detail{justify-content:space-between;padding:6px 0;font-size:.85rem;display:flex}.breed-card-detail .label{color:var(--color-text-muted)}.breed-card-detail .value{font-weight:500}.breed-card-description{color:var(--color-text-secondary);-webkit-line-clamp:3;-webkit-box-orient:vertical;margin-top:.75rem;font-size:.85rem;line-height:1.5;display:-webkit-box;overflow:hidden}.camera-container{border-radius:var(--radius-lg);background:#000;position:relative;overflow:hidden}.camera-container video{object-fit:cover;width:100%;max-height:350px}.camera-controls{justify-content:center;gap:12px;margin-top:1rem;display:flex}.camera-capture-btn{border:3px solid var(--color-accent);cursor:pointer;width:56px;height:56px;transition:all var(--transition-fast);background:0 0;border-radius:50%;justify-content:center;align-items:center;display:flex}.camera-capture-btn:hover{background:var(--color-accent-glow)}.camera-capture-btn:after{content:"";background:var(--color-accent);border-radius:50%;width:40px;height:40px}.loading-spinner{flex-direction:column;align-items:center;gap:1rem;padding:3rem;display:flex}.spinner{border:3px solid var(--color-border);border-top-color:var(--color-accent);border-radius:50%;width:40px;height:40px;animation:.8s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.footer{border-top:1px solid var(--color-border);text-align:center;color:var(--color-text-muted);margin-top:auto;padding:2rem;font-size:.85rem}.footer a{color:var(--color-accent)}.about-grid{grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.5rem;margin-top:2rem;display:grid}.about-card h3{color:var(--color-accent);margin-bottom:.75rem;font-size:1.1rem}.about-card p,.about-card li{color:var(--color-text-secondary);font-size:.9rem;line-height:1.7}.about-card ul{padding:0;list-style:none}.about-card li:before{content:"→ ";color:var(--color-accent)}.tips-box{border-radius:var(--radius-md);background:#3b82f614;border:1px solid #3b82f633;margin-top:1rem;padding:1rem 1.25rem}.tips-box h4{color:var(--color-info);margin-bottom:.5rem;font-size:.9rem}.tips-box li{color:var(--color-text-secondary);margin-bottom:4px;font-size:.8rem}@media (width<=768px){.hero h1{font-size:2rem}.page{padding:1rem}.breed-info-grid{grid-template-columns:1fr}.navbar-inner{padding:0}.nav-links a{padding:8px 10px;font-size:.8rem}.features-grid,.breeds-grid{grid-template-columns:1fr}}
frontend/dist/assets/index-sBN3tCmx.js ADDED
The diff for this file is too large to render. See raw diff
 
frontend/dist/favicon.svg ADDED
frontend/dist/icons.svg ADDED
frontend/dist/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>frontend</title>
8
+ <script type="module" crossorigin src="/assets/index-sBN3tCmx.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CWZymrby.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
frontend/eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist']),
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ reactHooks.configs.flat.recommended,
14
+ reactRefresh.configs.vite,
15
+ ],
16
+ languageOptions: {
17
+ ecmaVersion: 2020,
18
+ globals: globals.browser,
19
+ parserOptions: {
20
+ ecmaVersion: 'latest',
21
+ ecmaFeatures: { jsx: true },
22
+ sourceType: 'module',
23
+ },
24
+ },
25
+ rules: {
26
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27
+ },
28
+ },
29
+ ])
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>frontend</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
frontend/nginx.conf ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+ listen 80;
3
+ server_name localhost;
4
+ root /usr/share/nginx/html;
5
+ index index.html;
6
+
7
+ # SPA fallback
8
+ location / {
9
+ try_files $uri $uri/ /index.html;
10
+ }
11
+
12
+ # Proxy API calls to backend
13
+ location /api/ {
14
+ proxy_pass http://backend:8000/;
15
+ proxy_set_header Host $host;
16
+ proxy_set_header X-Real-IP $remote_addr;
17
+ }
18
+
19
+ # Cache static assets
20
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
21
+ expires 1y;
22
+ add_header Cache-Control "public, immutable";
23
+ }
24
+ }
frontend/package-lock.json ADDED
@@ -0,0 +1,2625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "frontend",
9
+ "version": "0.0.0",
10
+ "dependencies": {
11
+ "react": "^19.2.4",
12
+ "react-dom": "^19.2.4",
13
+ "react-router-dom": "^7.13.2"
14
+ },
15
+ "devDependencies": {
16
+ "@eslint/js": "^9.39.4",
17
+ "@types/react": "^19.2.14",
18
+ "@types/react-dom": "^19.2.3",
19
+ "@vitejs/plugin-react": "^6.0.1",
20
+ "eslint": "^9.39.4",
21
+ "eslint-plugin-react-hooks": "^7.0.1",
22
+ "eslint-plugin-react-refresh": "^0.5.2",
23
+ "globals": "^17.4.0",
24
+ "vite": "^8.0.1"
25
+ }
26
+ },
27
+ "node_modules/@babel/code-frame": {
28
+ "version": "7.29.0",
29
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
30
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
31
+ "dev": true,
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@babel/helper-validator-identifier": "^7.28.5",
35
+ "js-tokens": "^4.0.0",
36
+ "picocolors": "^1.1.1"
37
+ },
38
+ "engines": {
39
+ "node": ">=6.9.0"
40
+ }
41
+ },
42
+ "node_modules/@babel/compat-data": {
43
+ "version": "7.29.0",
44
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
45
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
46
+ "dev": true,
47
+ "license": "MIT",
48
+ "engines": {
49
+ "node": ">=6.9.0"
50
+ }
51
+ },
52
+ "node_modules/@babel/core": {
53
+ "version": "7.29.0",
54
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
55
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
56
+ "dev": true,
57
+ "license": "MIT",
58
+ "dependencies": {
59
+ "@babel/code-frame": "^7.29.0",
60
+ "@babel/generator": "^7.29.0",
61
+ "@babel/helper-compilation-targets": "^7.28.6",
62
+ "@babel/helper-module-transforms": "^7.28.6",
63
+ "@babel/helpers": "^7.28.6",
64
+ "@babel/parser": "^7.29.0",
65
+ "@babel/template": "^7.28.6",
66
+ "@babel/traverse": "^7.29.0",
67
+ "@babel/types": "^7.29.0",
68
+ "@jridgewell/remapping": "^2.3.5",
69
+ "convert-source-map": "^2.0.0",
70
+ "debug": "^4.1.0",
71
+ "gensync": "^1.0.0-beta.2",
72
+ "json5": "^2.2.3",
73
+ "semver": "^6.3.1"
74
+ },
75
+ "engines": {
76
+ "node": ">=6.9.0"
77
+ },
78
+ "funding": {
79
+ "type": "opencollective",
80
+ "url": "https://opencollective.com/babel"
81
+ }
82
+ },
83
+ "node_modules/@babel/generator": {
84
+ "version": "7.29.1",
85
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
86
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
87
+ "dev": true,
88
+ "license": "MIT",
89
+ "dependencies": {
90
+ "@babel/parser": "^7.29.0",
91
+ "@babel/types": "^7.29.0",
92
+ "@jridgewell/gen-mapping": "^0.3.12",
93
+ "@jridgewell/trace-mapping": "^0.3.28",
94
+ "jsesc": "^3.0.2"
95
+ },
96
+ "engines": {
97
+ "node": ">=6.9.0"
98
+ }
99
+ },
100
+ "node_modules/@babel/helper-compilation-targets": {
101
+ "version": "7.28.6",
102
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
103
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
104
+ "dev": true,
105
+ "license": "MIT",
106
+ "dependencies": {
107
+ "@babel/compat-data": "^7.28.6",
108
+ "@babel/helper-validator-option": "^7.27.1",
109
+ "browserslist": "^4.24.0",
110
+ "lru-cache": "^5.1.1",
111
+ "semver": "^6.3.1"
112
+ },
113
+ "engines": {
114
+ "node": ">=6.9.0"
115
+ }
116
+ },
117
+ "node_modules/@babel/helper-globals": {
118
+ "version": "7.28.0",
119
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
120
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
121
+ "dev": true,
122
+ "license": "MIT",
123
+ "engines": {
124
+ "node": ">=6.9.0"
125
+ }
126
+ },
127
+ "node_modules/@babel/helper-module-imports": {
128
+ "version": "7.28.6",
129
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
130
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
131
+ "dev": true,
132
+ "license": "MIT",
133
+ "dependencies": {
134
+ "@babel/traverse": "^7.28.6",
135
+ "@babel/types": "^7.28.6"
136
+ },
137
+ "engines": {
138
+ "node": ">=6.9.0"
139
+ }
140
+ },
141
+ "node_modules/@babel/helper-module-transforms": {
142
+ "version": "7.28.6",
143
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
144
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
145
+ "dev": true,
146
+ "license": "MIT",
147
+ "dependencies": {
148
+ "@babel/helper-module-imports": "^7.28.6",
149
+ "@babel/helper-validator-identifier": "^7.28.5",
150
+ "@babel/traverse": "^7.28.6"
151
+ },
152
+ "engines": {
153
+ "node": ">=6.9.0"
154
+ },
155
+ "peerDependencies": {
156
+ "@babel/core": "^7.0.0"
157
+ }
158
+ },
159
+ "node_modules/@babel/helper-string-parser": {
160
+ "version": "7.27.1",
161
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
162
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
163
+ "dev": true,
164
+ "license": "MIT",
165
+ "engines": {
166
+ "node": ">=6.9.0"
167
+ }
168
+ },
169
+ "node_modules/@babel/helper-validator-identifier": {
170
+ "version": "7.28.5",
171
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
172
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
173
+ "dev": true,
174
+ "license": "MIT",
175
+ "engines": {
176
+ "node": ">=6.9.0"
177
+ }
178
+ },
179
+ "node_modules/@babel/helper-validator-option": {
180
+ "version": "7.27.1",
181
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
182
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
183
+ "dev": true,
184
+ "license": "MIT",
185
+ "engines": {
186
+ "node": ">=6.9.0"
187
+ }
188
+ },
189
+ "node_modules/@babel/helpers": {
190
+ "version": "7.29.2",
191
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
192
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
193
+ "dev": true,
194
+ "license": "MIT",
195
+ "dependencies": {
196
+ "@babel/template": "^7.28.6",
197
+ "@babel/types": "^7.29.0"
198
+ },
199
+ "engines": {
200
+ "node": ">=6.9.0"
201
+ }
202
+ },
203
+ "node_modules/@babel/parser": {
204
+ "version": "7.29.2",
205
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
206
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
207
+ "dev": true,
208
+ "license": "MIT",
209
+ "dependencies": {
210
+ "@babel/types": "^7.29.0"
211
+ },
212
+ "bin": {
213
+ "parser": "bin/babel-parser.js"
214
+ },
215
+ "engines": {
216
+ "node": ">=6.0.0"
217
+ }
218
+ },
219
+ "node_modules/@babel/template": {
220
+ "version": "7.28.6",
221
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
222
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
223
+ "dev": true,
224
+ "license": "MIT",
225
+ "dependencies": {
226
+ "@babel/code-frame": "^7.28.6",
227
+ "@babel/parser": "^7.28.6",
228
+ "@babel/types": "^7.28.6"
229
+ },
230
+ "engines": {
231
+ "node": ">=6.9.0"
232
+ }
233
+ },
234
+ "node_modules/@babel/traverse": {
235
+ "version": "7.29.0",
236
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
237
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
238
+ "dev": true,
239
+ "license": "MIT",
240
+ "dependencies": {
241
+ "@babel/code-frame": "^7.29.0",
242
+ "@babel/generator": "^7.29.0",
243
+ "@babel/helper-globals": "^7.28.0",
244
+ "@babel/parser": "^7.29.0",
245
+ "@babel/template": "^7.28.6",
246
+ "@babel/types": "^7.29.0",
247
+ "debug": "^4.3.1"
248
+ },
249
+ "engines": {
250
+ "node": ">=6.9.0"
251
+ }
252
+ },
253
+ "node_modules/@babel/types": {
254
+ "version": "7.29.0",
255
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
256
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
257
+ "dev": true,
258
+ "license": "MIT",
259
+ "dependencies": {
260
+ "@babel/helper-string-parser": "^7.27.1",
261
+ "@babel/helper-validator-identifier": "^7.28.5"
262
+ },
263
+ "engines": {
264
+ "node": ">=6.9.0"
265
+ }
266
+ },
267
+ "node_modules/@eslint-community/eslint-utils": {
268
+ "version": "4.9.1",
269
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
270
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
271
+ "dev": true,
272
+ "license": "MIT",
273
+ "dependencies": {
274
+ "eslint-visitor-keys": "^3.4.3"
275
+ },
276
+ "engines": {
277
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
278
+ },
279
+ "funding": {
280
+ "url": "https://opencollective.com/eslint"
281
+ },
282
+ "peerDependencies": {
283
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
284
+ }
285
+ },
286
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
287
+ "version": "3.4.3",
288
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
289
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
290
+ "dev": true,
291
+ "license": "Apache-2.0",
292
+ "engines": {
293
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
294
+ },
295
+ "funding": {
296
+ "url": "https://opencollective.com/eslint"
297
+ }
298
+ },
299
+ "node_modules/@eslint-community/regexpp": {
300
+ "version": "4.12.2",
301
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
302
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
303
+ "dev": true,
304
+ "license": "MIT",
305
+ "engines": {
306
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
307
+ }
308
+ },
309
+ "node_modules/@eslint/config-array": {
310
+ "version": "0.21.2",
311
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
312
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
313
+ "dev": true,
314
+ "license": "Apache-2.0",
315
+ "dependencies": {
316
+ "@eslint/object-schema": "^2.1.7",
317
+ "debug": "^4.3.1",
318
+ "minimatch": "^3.1.5"
319
+ },
320
+ "engines": {
321
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
322
+ }
323
+ },
324
+ "node_modules/@eslint/config-helpers": {
325
+ "version": "0.4.2",
326
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
327
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
328
+ "dev": true,
329
+ "license": "Apache-2.0",
330
+ "dependencies": {
331
+ "@eslint/core": "^0.17.0"
332
+ },
333
+ "engines": {
334
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
335
+ }
336
+ },
337
+ "node_modules/@eslint/core": {
338
+ "version": "0.17.0",
339
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
340
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
341
+ "dev": true,
342
+ "license": "Apache-2.0",
343
+ "dependencies": {
344
+ "@types/json-schema": "^7.0.15"
345
+ },
346
+ "engines": {
347
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
348
+ }
349
+ },
350
+ "node_modules/@eslint/eslintrc": {
351
+ "version": "3.3.5",
352
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
353
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
354
+ "dev": true,
355
+ "license": "MIT",
356
+ "dependencies": {
357
+ "ajv": "^6.14.0",
358
+ "debug": "^4.3.2",
359
+ "espree": "^10.0.1",
360
+ "globals": "^14.0.0",
361
+ "ignore": "^5.2.0",
362
+ "import-fresh": "^3.2.1",
363
+ "js-yaml": "^4.1.1",
364
+ "minimatch": "^3.1.5",
365
+ "strip-json-comments": "^3.1.1"
366
+ },
367
+ "engines": {
368
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
369
+ },
370
+ "funding": {
371
+ "url": "https://opencollective.com/eslint"
372
+ }
373
+ },
374
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
375
+ "version": "14.0.0",
376
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
377
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
378
+ "dev": true,
379
+ "license": "MIT",
380
+ "engines": {
381
+ "node": ">=18"
382
+ },
383
+ "funding": {
384
+ "url": "https://github.com/sponsors/sindresorhus"
385
+ }
386
+ },
387
+ "node_modules/@eslint/js": {
388
+ "version": "9.39.4",
389
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
390
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
391
+ "dev": true,
392
+ "license": "MIT",
393
+ "engines": {
394
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
395
+ },
396
+ "funding": {
397
+ "url": "https://eslint.org/donate"
398
+ }
399
+ },
400
+ "node_modules/@eslint/object-schema": {
401
+ "version": "2.1.7",
402
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
403
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
404
+ "dev": true,
405
+ "license": "Apache-2.0",
406
+ "engines": {
407
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
408
+ }
409
+ },
410
+ "node_modules/@eslint/plugin-kit": {
411
+ "version": "0.4.1",
412
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
413
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
414
+ "dev": true,
415
+ "license": "Apache-2.0",
416
+ "dependencies": {
417
+ "@eslint/core": "^0.17.0",
418
+ "levn": "^0.4.1"
419
+ },
420
+ "engines": {
421
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
422
+ }
423
+ },
424
+ "node_modules/@humanfs/core": {
425
+ "version": "0.19.1",
426
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
427
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
428
+ "dev": true,
429
+ "license": "Apache-2.0",
430
+ "engines": {
431
+ "node": ">=18.18.0"
432
+ }
433
+ },
434
+ "node_modules/@humanfs/node": {
435
+ "version": "0.16.7",
436
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
437
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
438
+ "dev": true,
439
+ "license": "Apache-2.0",
440
+ "dependencies": {
441
+ "@humanfs/core": "^0.19.1",
442
+ "@humanwhocodes/retry": "^0.4.0"
443
+ },
444
+ "engines": {
445
+ "node": ">=18.18.0"
446
+ }
447
+ },
448
+ "node_modules/@humanwhocodes/module-importer": {
449
+ "version": "1.0.1",
450
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
451
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
452
+ "dev": true,
453
+ "license": "Apache-2.0",
454
+ "engines": {
455
+ "node": ">=12.22"
456
+ },
457
+ "funding": {
458
+ "type": "github",
459
+ "url": "https://github.com/sponsors/nzakas"
460
+ }
461
+ },
462
+ "node_modules/@humanwhocodes/retry": {
463
+ "version": "0.4.3",
464
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
465
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
466
+ "dev": true,
467
+ "license": "Apache-2.0",
468
+ "engines": {
469
+ "node": ">=18.18"
470
+ },
471
+ "funding": {
472
+ "type": "github",
473
+ "url": "https://github.com/sponsors/nzakas"
474
+ }
475
+ },
476
+ "node_modules/@jridgewell/gen-mapping": {
477
+ "version": "0.3.13",
478
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
479
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
480
+ "dev": true,
481
+ "license": "MIT",
482
+ "dependencies": {
483
+ "@jridgewell/sourcemap-codec": "^1.5.0",
484
+ "@jridgewell/trace-mapping": "^0.3.24"
485
+ }
486
+ },
487
+ "node_modules/@jridgewell/remapping": {
488
+ "version": "2.3.5",
489
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
490
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
491
+ "dev": true,
492
+ "license": "MIT",
493
+ "dependencies": {
494
+ "@jridgewell/gen-mapping": "^0.3.5",
495
+ "@jridgewell/trace-mapping": "^0.3.24"
496
+ }
497
+ },
498
+ "node_modules/@jridgewell/resolve-uri": {
499
+ "version": "3.1.2",
500
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
501
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
502
+ "dev": true,
503
+ "license": "MIT",
504
+ "engines": {
505
+ "node": ">=6.0.0"
506
+ }
507
+ },
508
+ "node_modules/@jridgewell/sourcemap-codec": {
509
+ "version": "1.5.5",
510
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
511
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
512
+ "dev": true,
513
+ "license": "MIT"
514
+ },
515
+ "node_modules/@jridgewell/trace-mapping": {
516
+ "version": "0.3.31",
517
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
518
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
519
+ "dev": true,
520
+ "license": "MIT",
521
+ "dependencies": {
522
+ "@jridgewell/resolve-uri": "^3.1.0",
523
+ "@jridgewell/sourcemap-codec": "^1.4.14"
524
+ }
525
+ },
526
+ "node_modules/@napi-rs/wasm-runtime": {
527
+ "version": "1.1.2",
528
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
529
+ "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
530
+ "dev": true,
531
+ "license": "MIT",
532
+ "optional": true,
533
+ "dependencies": {
534
+ "@tybys/wasm-util": "^0.10.1"
535
+ },
536
+ "funding": {
537
+ "type": "github",
538
+ "url": "https://github.com/sponsors/Brooooooklyn"
539
+ },
540
+ "peerDependencies": {
541
+ "@emnapi/core": "^1.7.1",
542
+ "@emnapi/runtime": "^1.7.1"
543
+ }
544
+ },
545
+ "node_modules/@oxc-project/types": {
546
+ "version": "0.122.0",
547
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
548
+ "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
549
+ "dev": true,
550
+ "license": "MIT",
551
+ "funding": {
552
+ "url": "https://github.com/sponsors/Boshen"
553
+ }
554
+ },
555
+ "node_modules/@rolldown/binding-android-arm64": {
556
+ "version": "1.0.0-rc.12",
557
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
558
+ "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
559
+ "cpu": [
560
+ "arm64"
561
+ ],
562
+ "dev": true,
563
+ "license": "MIT",
564
+ "optional": true,
565
+ "os": [
566
+ "android"
567
+ ],
568
+ "engines": {
569
+ "node": "^20.19.0 || >=22.12.0"
570
+ }
571
+ },
572
+ "node_modules/@rolldown/binding-darwin-arm64": {
573
+ "version": "1.0.0-rc.12",
574
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
575
+ "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
576
+ "cpu": [
577
+ "arm64"
578
+ ],
579
+ "dev": true,
580
+ "license": "MIT",
581
+ "optional": true,
582
+ "os": [
583
+ "darwin"
584
+ ],
585
+ "engines": {
586
+ "node": "^20.19.0 || >=22.12.0"
587
+ }
588
+ },
589
+ "node_modules/@rolldown/binding-darwin-x64": {
590
+ "version": "1.0.0-rc.12",
591
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
592
+ "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
593
+ "cpu": [
594
+ "x64"
595
+ ],
596
+ "dev": true,
597
+ "license": "MIT",
598
+ "optional": true,
599
+ "os": [
600
+ "darwin"
601
+ ],
602
+ "engines": {
603
+ "node": "^20.19.0 || >=22.12.0"
604
+ }
605
+ },
606
+ "node_modules/@rolldown/binding-freebsd-x64": {
607
+ "version": "1.0.0-rc.12",
608
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
609
+ "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
610
+ "cpu": [
611
+ "x64"
612
+ ],
613
+ "dev": true,
614
+ "license": "MIT",
615
+ "optional": true,
616
+ "os": [
617
+ "freebsd"
618
+ ],
619
+ "engines": {
620
+ "node": "^20.19.0 || >=22.12.0"
621
+ }
622
+ },
623
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
624
+ "version": "1.0.0-rc.12",
625
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
626
+ "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
627
+ "cpu": [
628
+ "arm"
629
+ ],
630
+ "dev": true,
631
+ "license": "MIT",
632
+ "optional": true,
633
+ "os": [
634
+ "linux"
635
+ ],
636
+ "engines": {
637
+ "node": "^20.19.0 || >=22.12.0"
638
+ }
639
+ },
640
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
641
+ "version": "1.0.0-rc.12",
642
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
643
+ "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
644
+ "cpu": [
645
+ "arm64"
646
+ ],
647
+ "dev": true,
648
+ "license": "MIT",
649
+ "optional": true,
650
+ "os": [
651
+ "linux"
652
+ ],
653
+ "engines": {
654
+ "node": "^20.19.0 || >=22.12.0"
655
+ }
656
+ },
657
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
658
+ "version": "1.0.0-rc.12",
659
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
660
+ "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
661
+ "cpu": [
662
+ "arm64"
663
+ ],
664
+ "dev": true,
665
+ "license": "MIT",
666
+ "optional": true,
667
+ "os": [
668
+ "linux"
669
+ ],
670
+ "engines": {
671
+ "node": "^20.19.0 || >=22.12.0"
672
+ }
673
+ },
674
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
675
+ "version": "1.0.0-rc.12",
676
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
677
+ "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
678
+ "cpu": [
679
+ "ppc64"
680
+ ],
681
+ "dev": true,
682
+ "license": "MIT",
683
+ "optional": true,
684
+ "os": [
685
+ "linux"
686
+ ],
687
+ "engines": {
688
+ "node": "^20.19.0 || >=22.12.0"
689
+ }
690
+ },
691
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
692
+ "version": "1.0.0-rc.12",
693
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
694
+ "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
695
+ "cpu": [
696
+ "s390x"
697
+ ],
698
+ "dev": true,
699
+ "license": "MIT",
700
+ "optional": true,
701
+ "os": [
702
+ "linux"
703
+ ],
704
+ "engines": {
705
+ "node": "^20.19.0 || >=22.12.0"
706
+ }
707
+ },
708
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
709
+ "version": "1.0.0-rc.12",
710
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
711
+ "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
712
+ "cpu": [
713
+ "x64"
714
+ ],
715
+ "dev": true,
716
+ "license": "MIT",
717
+ "optional": true,
718
+ "os": [
719
+ "linux"
720
+ ],
721
+ "engines": {
722
+ "node": "^20.19.0 || >=22.12.0"
723
+ }
724
+ },
725
+ "node_modules/@rolldown/binding-linux-x64-musl": {
726
+ "version": "1.0.0-rc.12",
727
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
728
+ "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
729
+ "cpu": [
730
+ "x64"
731
+ ],
732
+ "dev": true,
733
+ "license": "MIT",
734
+ "optional": true,
735
+ "os": [
736
+ "linux"
737
+ ],
738
+ "engines": {
739
+ "node": "^20.19.0 || >=22.12.0"
740
+ }
741
+ },
742
+ "node_modules/@rolldown/binding-openharmony-arm64": {
743
+ "version": "1.0.0-rc.12",
744
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
745
+ "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
746
+ "cpu": [
747
+ "arm64"
748
+ ],
749
+ "dev": true,
750
+ "license": "MIT",
751
+ "optional": true,
752
+ "os": [
753
+ "openharmony"
754
+ ],
755
+ "engines": {
756
+ "node": "^20.19.0 || >=22.12.0"
757
+ }
758
+ },
759
+ "node_modules/@rolldown/binding-wasm32-wasi": {
760
+ "version": "1.0.0-rc.12",
761
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
762
+ "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
763
+ "cpu": [
764
+ "wasm32"
765
+ ],
766
+ "dev": true,
767
+ "license": "MIT",
768
+ "optional": true,
769
+ "dependencies": {
770
+ "@napi-rs/wasm-runtime": "^1.1.1"
771
+ },
772
+ "engines": {
773
+ "node": ">=14.0.0"
774
+ }
775
+ },
776
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
777
+ "version": "1.0.0-rc.12",
778
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
779
+ "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
780
+ "cpu": [
781
+ "arm64"
782
+ ],
783
+ "dev": true,
784
+ "license": "MIT",
785
+ "optional": true,
786
+ "os": [
787
+ "win32"
788
+ ],
789
+ "engines": {
790
+ "node": "^20.19.0 || >=22.12.0"
791
+ }
792
+ },
793
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
794
+ "version": "1.0.0-rc.12",
795
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
796
+ "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
797
+ "cpu": [
798
+ "x64"
799
+ ],
800
+ "dev": true,
801
+ "license": "MIT",
802
+ "optional": true,
803
+ "os": [
804
+ "win32"
805
+ ],
806
+ "engines": {
807
+ "node": "^20.19.0 || >=22.12.0"
808
+ }
809
+ },
810
+ "node_modules/@rolldown/pluginutils": {
811
+ "version": "1.0.0-rc.7",
812
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
813
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
814
+ "dev": true,
815
+ "license": "MIT"
816
+ },
817
+ "node_modules/@tybys/wasm-util": {
818
+ "version": "0.10.1",
819
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
820
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
821
+ "dev": true,
822
+ "license": "MIT",
823
+ "optional": true,
824
+ "dependencies": {
825
+ "tslib": "^2.4.0"
826
+ }
827
+ },
828
+ "node_modules/@types/estree": {
829
+ "version": "1.0.8",
830
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
831
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
832
+ "dev": true,
833
+ "license": "MIT"
834
+ },
835
+ "node_modules/@types/json-schema": {
836
+ "version": "7.0.15",
837
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
838
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
839
+ "dev": true,
840
+ "license": "MIT"
841
+ },
842
+ "node_modules/@types/react": {
843
+ "version": "19.2.14",
844
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
845
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
846
+ "dev": true,
847
+ "license": "MIT",
848
+ "dependencies": {
849
+ "csstype": "^3.2.2"
850
+ }
851
+ },
852
+ "node_modules/@types/react-dom": {
853
+ "version": "19.2.3",
854
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
855
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
856
+ "dev": true,
857
+ "license": "MIT",
858
+ "peerDependencies": {
859
+ "@types/react": "^19.2.0"
860
+ }
861
+ },
862
+ "node_modules/@vitejs/plugin-react": {
863
+ "version": "6.0.1",
864
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
865
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
866
+ "dev": true,
867
+ "license": "MIT",
868
+ "dependencies": {
869
+ "@rolldown/pluginutils": "1.0.0-rc.7"
870
+ },
871
+ "engines": {
872
+ "node": "^20.19.0 || >=22.12.0"
873
+ },
874
+ "peerDependencies": {
875
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
876
+ "babel-plugin-react-compiler": "^1.0.0",
877
+ "vite": "^8.0.0"
878
+ },
879
+ "peerDependenciesMeta": {
880
+ "@rolldown/plugin-babel": {
881
+ "optional": true
882
+ },
883
+ "babel-plugin-react-compiler": {
884
+ "optional": true
885
+ }
886
+ }
887
+ },
888
+ "node_modules/acorn": {
889
+ "version": "8.16.0",
890
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
891
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
892
+ "dev": true,
893
+ "license": "MIT",
894
+ "bin": {
895
+ "acorn": "bin/acorn"
896
+ },
897
+ "engines": {
898
+ "node": ">=0.4.0"
899
+ }
900
+ },
901
+ "node_modules/acorn-jsx": {
902
+ "version": "5.3.2",
903
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
904
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
905
+ "dev": true,
906
+ "license": "MIT",
907
+ "peerDependencies": {
908
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
909
+ }
910
+ },
911
+ "node_modules/ajv": {
912
+ "version": "6.14.0",
913
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
914
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
915
+ "dev": true,
916
+ "license": "MIT",
917
+ "dependencies": {
918
+ "fast-deep-equal": "^3.1.1",
919
+ "fast-json-stable-stringify": "^2.0.0",
920
+ "json-schema-traverse": "^0.4.1",
921
+ "uri-js": "^4.2.2"
922
+ },
923
+ "funding": {
924
+ "type": "github",
925
+ "url": "https://github.com/sponsors/epoberezkin"
926
+ }
927
+ },
928
+ "node_modules/ansi-styles": {
929
+ "version": "4.3.0",
930
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
931
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
932
+ "dev": true,
933
+ "license": "MIT",
934
+ "dependencies": {
935
+ "color-convert": "^2.0.1"
936
+ },
937
+ "engines": {
938
+ "node": ">=8"
939
+ },
940
+ "funding": {
941
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
942
+ }
943
+ },
944
+ "node_modules/argparse": {
945
+ "version": "2.0.1",
946
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
947
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
948
+ "dev": true,
949
+ "license": "Python-2.0"
950
+ },
951
+ "node_modules/balanced-match": {
952
+ "version": "1.0.2",
953
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
954
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
955
+ "dev": true,
956
+ "license": "MIT"
957
+ },
958
+ "node_modules/baseline-browser-mapping": {
959
+ "version": "2.10.12",
960
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz",
961
+ "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==",
962
+ "dev": true,
963
+ "license": "Apache-2.0",
964
+ "bin": {
965
+ "baseline-browser-mapping": "dist/cli.cjs"
966
+ },
967
+ "engines": {
968
+ "node": ">=6.0.0"
969
+ }
970
+ },
971
+ "node_modules/brace-expansion": {
972
+ "version": "1.1.13",
973
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
974
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
975
+ "dev": true,
976
+ "license": "MIT",
977
+ "dependencies": {
978
+ "balanced-match": "^1.0.0",
979
+ "concat-map": "0.0.1"
980
+ }
981
+ },
982
+ "node_modules/browserslist": {
983
+ "version": "4.28.1",
984
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
985
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
986
+ "dev": true,
987
+ "funding": [
988
+ {
989
+ "type": "opencollective",
990
+ "url": "https://opencollective.com/browserslist"
991
+ },
992
+ {
993
+ "type": "tidelift",
994
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
995
+ },
996
+ {
997
+ "type": "github",
998
+ "url": "https://github.com/sponsors/ai"
999
+ }
1000
+ ],
1001
+ "license": "MIT",
1002
+ "dependencies": {
1003
+ "baseline-browser-mapping": "^2.9.0",
1004
+ "caniuse-lite": "^1.0.30001759",
1005
+ "electron-to-chromium": "^1.5.263",
1006
+ "node-releases": "^2.0.27",
1007
+ "update-browserslist-db": "^1.2.0"
1008
+ },
1009
+ "bin": {
1010
+ "browserslist": "cli.js"
1011
+ },
1012
+ "engines": {
1013
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1014
+ }
1015
+ },
1016
+ "node_modules/callsites": {
1017
+ "version": "3.1.0",
1018
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
1019
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
1020
+ "dev": true,
1021
+ "license": "MIT",
1022
+ "engines": {
1023
+ "node": ">=6"
1024
+ }
1025
+ },
1026
+ "node_modules/caniuse-lite": {
1027
+ "version": "1.0.30001781",
1028
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
1029
+ "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
1030
+ "dev": true,
1031
+ "funding": [
1032
+ {
1033
+ "type": "opencollective",
1034
+ "url": "https://opencollective.com/browserslist"
1035
+ },
1036
+ {
1037
+ "type": "tidelift",
1038
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1039
+ },
1040
+ {
1041
+ "type": "github",
1042
+ "url": "https://github.com/sponsors/ai"
1043
+ }
1044
+ ],
1045
+ "license": "CC-BY-4.0"
1046
+ },
1047
+ "node_modules/chalk": {
1048
+ "version": "4.1.2",
1049
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
1050
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
1051
+ "dev": true,
1052
+ "license": "MIT",
1053
+ "dependencies": {
1054
+ "ansi-styles": "^4.1.0",
1055
+ "supports-color": "^7.1.0"
1056
+ },
1057
+ "engines": {
1058
+ "node": ">=10"
1059
+ },
1060
+ "funding": {
1061
+ "url": "https://github.com/chalk/chalk?sponsor=1"
1062
+ }
1063
+ },
1064
+ "node_modules/color-convert": {
1065
+ "version": "2.0.1",
1066
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1067
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1068
+ "dev": true,
1069
+ "license": "MIT",
1070
+ "dependencies": {
1071
+ "color-name": "~1.1.4"
1072
+ },
1073
+ "engines": {
1074
+ "node": ">=7.0.0"
1075
+ }
1076
+ },
1077
+ "node_modules/color-name": {
1078
+ "version": "1.1.4",
1079
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1080
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1081
+ "dev": true,
1082
+ "license": "MIT"
1083
+ },
1084
+ "node_modules/concat-map": {
1085
+ "version": "0.0.1",
1086
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1087
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
1088
+ "dev": true,
1089
+ "license": "MIT"
1090
+ },
1091
+ "node_modules/convert-source-map": {
1092
+ "version": "2.0.0",
1093
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1094
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1095
+ "dev": true,
1096
+ "license": "MIT"
1097
+ },
1098
+ "node_modules/cookie": {
1099
+ "version": "1.1.1",
1100
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
1101
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
1102
+ "license": "MIT",
1103
+ "engines": {
1104
+ "node": ">=18"
1105
+ },
1106
+ "funding": {
1107
+ "type": "opencollective",
1108
+ "url": "https://opencollective.com/express"
1109
+ }
1110
+ },
1111
+ "node_modules/cross-spawn": {
1112
+ "version": "7.0.6",
1113
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
1114
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
1115
+ "dev": true,
1116
+ "license": "MIT",
1117
+ "dependencies": {
1118
+ "path-key": "^3.1.0",
1119
+ "shebang-command": "^2.0.0",
1120
+ "which": "^2.0.1"
1121
+ },
1122
+ "engines": {
1123
+ "node": ">= 8"
1124
+ }
1125
+ },
1126
+ "node_modules/csstype": {
1127
+ "version": "3.2.3",
1128
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1129
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1130
+ "dev": true,
1131
+ "license": "MIT"
1132
+ },
1133
+ "node_modules/debug": {
1134
+ "version": "4.4.3",
1135
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1136
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1137
+ "dev": true,
1138
+ "license": "MIT",
1139
+ "dependencies": {
1140
+ "ms": "^2.1.3"
1141
+ },
1142
+ "engines": {
1143
+ "node": ">=6.0"
1144
+ },
1145
+ "peerDependenciesMeta": {
1146
+ "supports-color": {
1147
+ "optional": true
1148
+ }
1149
+ }
1150
+ },
1151
+ "node_modules/deep-is": {
1152
+ "version": "0.1.4",
1153
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
1154
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
1155
+ "dev": true,
1156
+ "license": "MIT"
1157
+ },
1158
+ "node_modules/detect-libc": {
1159
+ "version": "2.1.2",
1160
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
1161
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
1162
+ "dev": true,
1163
+ "license": "Apache-2.0",
1164
+ "engines": {
1165
+ "node": ">=8"
1166
+ }
1167
+ },
1168
+ "node_modules/electron-to-chromium": {
1169
+ "version": "1.5.328",
1170
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz",
1171
+ "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==",
1172
+ "dev": true,
1173
+ "license": "ISC"
1174
+ },
1175
+ "node_modules/escalade": {
1176
+ "version": "3.2.0",
1177
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1178
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1179
+ "dev": true,
1180
+ "license": "MIT",
1181
+ "engines": {
1182
+ "node": ">=6"
1183
+ }
1184
+ },
1185
+ "node_modules/escape-string-regexp": {
1186
+ "version": "4.0.0",
1187
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
1188
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
1189
+ "dev": true,
1190
+ "license": "MIT",
1191
+ "engines": {
1192
+ "node": ">=10"
1193
+ },
1194
+ "funding": {
1195
+ "url": "https://github.com/sponsors/sindresorhus"
1196
+ }
1197
+ },
1198
+ "node_modules/eslint": {
1199
+ "version": "9.39.4",
1200
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
1201
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
1202
+ "dev": true,
1203
+ "license": "MIT",
1204
+ "dependencies": {
1205
+ "@eslint-community/eslint-utils": "^4.8.0",
1206
+ "@eslint-community/regexpp": "^4.12.1",
1207
+ "@eslint/config-array": "^0.21.2",
1208
+ "@eslint/config-helpers": "^0.4.2",
1209
+ "@eslint/core": "^0.17.0",
1210
+ "@eslint/eslintrc": "^3.3.5",
1211
+ "@eslint/js": "9.39.4",
1212
+ "@eslint/plugin-kit": "^0.4.1",
1213
+ "@humanfs/node": "^0.16.6",
1214
+ "@humanwhocodes/module-importer": "^1.0.1",
1215
+ "@humanwhocodes/retry": "^0.4.2",
1216
+ "@types/estree": "^1.0.6",
1217
+ "ajv": "^6.14.0",
1218
+ "chalk": "^4.0.0",
1219
+ "cross-spawn": "^7.0.6",
1220
+ "debug": "^4.3.2",
1221
+ "escape-string-regexp": "^4.0.0",
1222
+ "eslint-scope": "^8.4.0",
1223
+ "eslint-visitor-keys": "^4.2.1",
1224
+ "espree": "^10.4.0",
1225
+ "esquery": "^1.5.0",
1226
+ "esutils": "^2.0.2",
1227
+ "fast-deep-equal": "^3.1.3",
1228
+ "file-entry-cache": "^8.0.0",
1229
+ "find-up": "^5.0.0",
1230
+ "glob-parent": "^6.0.2",
1231
+ "ignore": "^5.2.0",
1232
+ "imurmurhash": "^0.1.4",
1233
+ "is-glob": "^4.0.0",
1234
+ "json-stable-stringify-without-jsonify": "^1.0.1",
1235
+ "lodash.merge": "^4.6.2",
1236
+ "minimatch": "^3.1.5",
1237
+ "natural-compare": "^1.4.0",
1238
+ "optionator": "^0.9.3"
1239
+ },
1240
+ "bin": {
1241
+ "eslint": "bin/eslint.js"
1242
+ },
1243
+ "engines": {
1244
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1245
+ },
1246
+ "funding": {
1247
+ "url": "https://eslint.org/donate"
1248
+ },
1249
+ "peerDependencies": {
1250
+ "jiti": "*"
1251
+ },
1252
+ "peerDependenciesMeta": {
1253
+ "jiti": {
1254
+ "optional": true
1255
+ }
1256
+ }
1257
+ },
1258
+ "node_modules/eslint-plugin-react-hooks": {
1259
+ "version": "7.0.1",
1260
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
1261
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
1262
+ "dev": true,
1263
+ "license": "MIT",
1264
+ "dependencies": {
1265
+ "@babel/core": "^7.24.4",
1266
+ "@babel/parser": "^7.24.4",
1267
+ "hermes-parser": "^0.25.1",
1268
+ "zod": "^3.25.0 || ^4.0.0",
1269
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
1270
+ },
1271
+ "engines": {
1272
+ "node": ">=18"
1273
+ },
1274
+ "peerDependencies": {
1275
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
1276
+ }
1277
+ },
1278
+ "node_modules/eslint-plugin-react-refresh": {
1279
+ "version": "0.5.2",
1280
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
1281
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
1282
+ "dev": true,
1283
+ "license": "MIT",
1284
+ "peerDependencies": {
1285
+ "eslint": "^9 || ^10"
1286
+ }
1287
+ },
1288
+ "node_modules/eslint-scope": {
1289
+ "version": "8.4.0",
1290
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
1291
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
1292
+ "dev": true,
1293
+ "license": "BSD-2-Clause",
1294
+ "dependencies": {
1295
+ "esrecurse": "^4.3.0",
1296
+ "estraverse": "^5.2.0"
1297
+ },
1298
+ "engines": {
1299
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1300
+ },
1301
+ "funding": {
1302
+ "url": "https://opencollective.com/eslint"
1303
+ }
1304
+ },
1305
+ "node_modules/eslint-visitor-keys": {
1306
+ "version": "4.2.1",
1307
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
1308
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
1309
+ "dev": true,
1310
+ "license": "Apache-2.0",
1311
+ "engines": {
1312
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1313
+ },
1314
+ "funding": {
1315
+ "url": "https://opencollective.com/eslint"
1316
+ }
1317
+ },
1318
+ "node_modules/espree": {
1319
+ "version": "10.4.0",
1320
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
1321
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
1322
+ "dev": true,
1323
+ "license": "BSD-2-Clause",
1324
+ "dependencies": {
1325
+ "acorn": "^8.15.0",
1326
+ "acorn-jsx": "^5.3.2",
1327
+ "eslint-visitor-keys": "^4.2.1"
1328
+ },
1329
+ "engines": {
1330
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1331
+ },
1332
+ "funding": {
1333
+ "url": "https://opencollective.com/eslint"
1334
+ }
1335
+ },
1336
+ "node_modules/esquery": {
1337
+ "version": "1.7.0",
1338
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
1339
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
1340
+ "dev": true,
1341
+ "license": "BSD-3-Clause",
1342
+ "dependencies": {
1343
+ "estraverse": "^5.1.0"
1344
+ },
1345
+ "engines": {
1346
+ "node": ">=0.10"
1347
+ }
1348
+ },
1349
+ "node_modules/esrecurse": {
1350
+ "version": "4.3.0",
1351
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
1352
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
1353
+ "dev": true,
1354
+ "license": "BSD-2-Clause",
1355
+ "dependencies": {
1356
+ "estraverse": "^5.2.0"
1357
+ },
1358
+ "engines": {
1359
+ "node": ">=4.0"
1360
+ }
1361
+ },
1362
+ "node_modules/estraverse": {
1363
+ "version": "5.3.0",
1364
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
1365
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
1366
+ "dev": true,
1367
+ "license": "BSD-2-Clause",
1368
+ "engines": {
1369
+ "node": ">=4.0"
1370
+ }
1371
+ },
1372
+ "node_modules/esutils": {
1373
+ "version": "2.0.3",
1374
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
1375
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
1376
+ "dev": true,
1377
+ "license": "BSD-2-Clause",
1378
+ "engines": {
1379
+ "node": ">=0.10.0"
1380
+ }
1381
+ },
1382
+ "node_modules/fast-deep-equal": {
1383
+ "version": "3.1.3",
1384
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1385
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1386
+ "dev": true,
1387
+ "license": "MIT"
1388
+ },
1389
+ "node_modules/fast-json-stable-stringify": {
1390
+ "version": "2.1.0",
1391
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
1392
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
1393
+ "dev": true,
1394
+ "license": "MIT"
1395
+ },
1396
+ "node_modules/fast-levenshtein": {
1397
+ "version": "2.0.6",
1398
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
1399
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
1400
+ "dev": true,
1401
+ "license": "MIT"
1402
+ },
1403
+ "node_modules/fdir": {
1404
+ "version": "6.5.0",
1405
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1406
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1407
+ "dev": true,
1408
+ "license": "MIT",
1409
+ "engines": {
1410
+ "node": ">=12.0.0"
1411
+ },
1412
+ "peerDependencies": {
1413
+ "picomatch": "^3 || ^4"
1414
+ },
1415
+ "peerDependenciesMeta": {
1416
+ "picomatch": {
1417
+ "optional": true
1418
+ }
1419
+ }
1420
+ },
1421
+ "node_modules/file-entry-cache": {
1422
+ "version": "8.0.0",
1423
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
1424
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
1425
+ "dev": true,
1426
+ "license": "MIT",
1427
+ "dependencies": {
1428
+ "flat-cache": "^4.0.0"
1429
+ },
1430
+ "engines": {
1431
+ "node": ">=16.0.0"
1432
+ }
1433
+ },
1434
+ "node_modules/find-up": {
1435
+ "version": "5.0.0",
1436
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
1437
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
1438
+ "dev": true,
1439
+ "license": "MIT",
1440
+ "dependencies": {
1441
+ "locate-path": "^6.0.0",
1442
+ "path-exists": "^4.0.0"
1443
+ },
1444
+ "engines": {
1445
+ "node": ">=10"
1446
+ },
1447
+ "funding": {
1448
+ "url": "https://github.com/sponsors/sindresorhus"
1449
+ }
1450
+ },
1451
+ "node_modules/flat-cache": {
1452
+ "version": "4.0.1",
1453
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
1454
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
1455
+ "dev": true,
1456
+ "license": "MIT",
1457
+ "dependencies": {
1458
+ "flatted": "^3.2.9",
1459
+ "keyv": "^4.5.4"
1460
+ },
1461
+ "engines": {
1462
+ "node": ">=16"
1463
+ }
1464
+ },
1465
+ "node_modules/flatted": {
1466
+ "version": "3.4.2",
1467
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
1468
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
1469
+ "dev": true,
1470
+ "license": "ISC"
1471
+ },
1472
+ "node_modules/fsevents": {
1473
+ "version": "2.3.3",
1474
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1475
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1476
+ "dev": true,
1477
+ "hasInstallScript": true,
1478
+ "license": "MIT",
1479
+ "optional": true,
1480
+ "os": [
1481
+ "darwin"
1482
+ ],
1483
+ "engines": {
1484
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1485
+ }
1486
+ },
1487
+ "node_modules/gensync": {
1488
+ "version": "1.0.0-beta.2",
1489
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1490
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1491
+ "dev": true,
1492
+ "license": "MIT",
1493
+ "engines": {
1494
+ "node": ">=6.9.0"
1495
+ }
1496
+ },
1497
+ "node_modules/glob-parent": {
1498
+ "version": "6.0.2",
1499
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1500
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1501
+ "dev": true,
1502
+ "license": "ISC",
1503
+ "dependencies": {
1504
+ "is-glob": "^4.0.3"
1505
+ },
1506
+ "engines": {
1507
+ "node": ">=10.13.0"
1508
+ }
1509
+ },
1510
+ "node_modules/globals": {
1511
+ "version": "17.4.0",
1512
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
1513
+ "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
1514
+ "dev": true,
1515
+ "license": "MIT",
1516
+ "engines": {
1517
+ "node": ">=18"
1518
+ },
1519
+ "funding": {
1520
+ "url": "https://github.com/sponsors/sindresorhus"
1521
+ }
1522
+ },
1523
+ "node_modules/has-flag": {
1524
+ "version": "4.0.0",
1525
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1526
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
1527
+ "dev": true,
1528
+ "license": "MIT",
1529
+ "engines": {
1530
+ "node": ">=8"
1531
+ }
1532
+ },
1533
+ "node_modules/hermes-estree": {
1534
+ "version": "0.25.1",
1535
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
1536
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
1537
+ "dev": true,
1538
+ "license": "MIT"
1539
+ },
1540
+ "node_modules/hermes-parser": {
1541
+ "version": "0.25.1",
1542
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
1543
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
1544
+ "dev": true,
1545
+ "license": "MIT",
1546
+ "dependencies": {
1547
+ "hermes-estree": "0.25.1"
1548
+ }
1549
+ },
1550
+ "node_modules/ignore": {
1551
+ "version": "5.3.2",
1552
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
1553
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
1554
+ "dev": true,
1555
+ "license": "MIT",
1556
+ "engines": {
1557
+ "node": ">= 4"
1558
+ }
1559
+ },
1560
+ "node_modules/import-fresh": {
1561
+ "version": "3.3.1",
1562
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
1563
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
1564
+ "dev": true,
1565
+ "license": "MIT",
1566
+ "dependencies": {
1567
+ "parent-module": "^1.0.0",
1568
+ "resolve-from": "^4.0.0"
1569
+ },
1570
+ "engines": {
1571
+ "node": ">=6"
1572
+ },
1573
+ "funding": {
1574
+ "url": "https://github.com/sponsors/sindresorhus"
1575
+ }
1576
+ },
1577
+ "node_modules/imurmurhash": {
1578
+ "version": "0.1.4",
1579
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
1580
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
1581
+ "dev": true,
1582
+ "license": "MIT",
1583
+ "engines": {
1584
+ "node": ">=0.8.19"
1585
+ }
1586
+ },
1587
+ "node_modules/is-extglob": {
1588
+ "version": "2.1.1",
1589
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1590
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1591
+ "dev": true,
1592
+ "license": "MIT",
1593
+ "engines": {
1594
+ "node": ">=0.10.0"
1595
+ }
1596
+ },
1597
+ "node_modules/is-glob": {
1598
+ "version": "4.0.3",
1599
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1600
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1601
+ "dev": true,
1602
+ "license": "MIT",
1603
+ "dependencies": {
1604
+ "is-extglob": "^2.1.1"
1605
+ },
1606
+ "engines": {
1607
+ "node": ">=0.10.0"
1608
+ }
1609
+ },
1610
+ "node_modules/isexe": {
1611
+ "version": "2.0.0",
1612
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1613
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
1614
+ "dev": true,
1615
+ "license": "ISC"
1616
+ },
1617
+ "node_modules/js-tokens": {
1618
+ "version": "4.0.0",
1619
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1620
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1621
+ "dev": true,
1622
+ "license": "MIT"
1623
+ },
1624
+ "node_modules/js-yaml": {
1625
+ "version": "4.1.1",
1626
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
1627
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
1628
+ "dev": true,
1629
+ "license": "MIT",
1630
+ "dependencies": {
1631
+ "argparse": "^2.0.1"
1632
+ },
1633
+ "bin": {
1634
+ "js-yaml": "bin/js-yaml.js"
1635
+ }
1636
+ },
1637
+ "node_modules/jsesc": {
1638
+ "version": "3.1.0",
1639
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1640
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1641
+ "dev": true,
1642
+ "license": "MIT",
1643
+ "bin": {
1644
+ "jsesc": "bin/jsesc"
1645
+ },
1646
+ "engines": {
1647
+ "node": ">=6"
1648
+ }
1649
+ },
1650
+ "node_modules/json-buffer": {
1651
+ "version": "3.0.1",
1652
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
1653
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
1654
+ "dev": true,
1655
+ "license": "MIT"
1656
+ },
1657
+ "node_modules/json-schema-traverse": {
1658
+ "version": "0.4.1",
1659
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
1660
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
1661
+ "dev": true,
1662
+ "license": "MIT"
1663
+ },
1664
+ "node_modules/json-stable-stringify-without-jsonify": {
1665
+ "version": "1.0.1",
1666
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
1667
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
1668
+ "dev": true,
1669
+ "license": "MIT"
1670
+ },
1671
+ "node_modules/json5": {
1672
+ "version": "2.2.3",
1673
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1674
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1675
+ "dev": true,
1676
+ "license": "MIT",
1677
+ "bin": {
1678
+ "json5": "lib/cli.js"
1679
+ },
1680
+ "engines": {
1681
+ "node": ">=6"
1682
+ }
1683
+ },
1684
+ "node_modules/keyv": {
1685
+ "version": "4.5.4",
1686
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
1687
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
1688
+ "dev": true,
1689
+ "license": "MIT",
1690
+ "dependencies": {
1691
+ "json-buffer": "3.0.1"
1692
+ }
1693
+ },
1694
+ "node_modules/levn": {
1695
+ "version": "0.4.1",
1696
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
1697
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
1698
+ "dev": true,
1699
+ "license": "MIT",
1700
+ "dependencies": {
1701
+ "prelude-ls": "^1.2.1",
1702
+ "type-check": "~0.4.0"
1703
+ },
1704
+ "engines": {
1705
+ "node": ">= 0.8.0"
1706
+ }
1707
+ },
1708
+ "node_modules/lightningcss": {
1709
+ "version": "1.32.0",
1710
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
1711
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
1712
+ "dev": true,
1713
+ "license": "MPL-2.0",
1714
+ "dependencies": {
1715
+ "detect-libc": "^2.0.3"
1716
+ },
1717
+ "engines": {
1718
+ "node": ">= 12.0.0"
1719
+ },
1720
+ "funding": {
1721
+ "type": "opencollective",
1722
+ "url": "https://opencollective.com/parcel"
1723
+ },
1724
+ "optionalDependencies": {
1725
+ "lightningcss-android-arm64": "1.32.0",
1726
+ "lightningcss-darwin-arm64": "1.32.0",
1727
+ "lightningcss-darwin-x64": "1.32.0",
1728
+ "lightningcss-freebsd-x64": "1.32.0",
1729
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
1730
+ "lightningcss-linux-arm64-gnu": "1.32.0",
1731
+ "lightningcss-linux-arm64-musl": "1.32.0",
1732
+ "lightningcss-linux-x64-gnu": "1.32.0",
1733
+ "lightningcss-linux-x64-musl": "1.32.0",
1734
+ "lightningcss-win32-arm64-msvc": "1.32.0",
1735
+ "lightningcss-win32-x64-msvc": "1.32.0"
1736
+ }
1737
+ },
1738
+ "node_modules/lightningcss-android-arm64": {
1739
+ "version": "1.32.0",
1740
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
1741
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
1742
+ "cpu": [
1743
+ "arm64"
1744
+ ],
1745
+ "dev": true,
1746
+ "license": "MPL-2.0",
1747
+ "optional": true,
1748
+ "os": [
1749
+ "android"
1750
+ ],
1751
+ "engines": {
1752
+ "node": ">= 12.0.0"
1753
+ },
1754
+ "funding": {
1755
+ "type": "opencollective",
1756
+ "url": "https://opencollective.com/parcel"
1757
+ }
1758
+ },
1759
+ "node_modules/lightningcss-darwin-arm64": {
1760
+ "version": "1.32.0",
1761
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
1762
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
1763
+ "cpu": [
1764
+ "arm64"
1765
+ ],
1766
+ "dev": true,
1767
+ "license": "MPL-2.0",
1768
+ "optional": true,
1769
+ "os": [
1770
+ "darwin"
1771
+ ],
1772
+ "engines": {
1773
+ "node": ">= 12.0.0"
1774
+ },
1775
+ "funding": {
1776
+ "type": "opencollective",
1777
+ "url": "https://opencollective.com/parcel"
1778
+ }
1779
+ },
1780
+ "node_modules/lightningcss-darwin-x64": {
1781
+ "version": "1.32.0",
1782
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
1783
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
1784
+ "cpu": [
1785
+ "x64"
1786
+ ],
1787
+ "dev": true,
1788
+ "license": "MPL-2.0",
1789
+ "optional": true,
1790
+ "os": [
1791
+ "darwin"
1792
+ ],
1793
+ "engines": {
1794
+ "node": ">= 12.0.0"
1795
+ },
1796
+ "funding": {
1797
+ "type": "opencollective",
1798
+ "url": "https://opencollective.com/parcel"
1799
+ }
1800
+ },
1801
+ "node_modules/lightningcss-freebsd-x64": {
1802
+ "version": "1.32.0",
1803
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
1804
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
1805
+ "cpu": [
1806
+ "x64"
1807
+ ],
1808
+ "dev": true,
1809
+ "license": "MPL-2.0",
1810
+ "optional": true,
1811
+ "os": [
1812
+ "freebsd"
1813
+ ],
1814
+ "engines": {
1815
+ "node": ">= 12.0.0"
1816
+ },
1817
+ "funding": {
1818
+ "type": "opencollective",
1819
+ "url": "https://opencollective.com/parcel"
1820
+ }
1821
+ },
1822
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
1823
+ "version": "1.32.0",
1824
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
1825
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
1826
+ "cpu": [
1827
+ "arm"
1828
+ ],
1829
+ "dev": true,
1830
+ "license": "MPL-2.0",
1831
+ "optional": true,
1832
+ "os": [
1833
+ "linux"
1834
+ ],
1835
+ "engines": {
1836
+ "node": ">= 12.0.0"
1837
+ },
1838
+ "funding": {
1839
+ "type": "opencollective",
1840
+ "url": "https://opencollective.com/parcel"
1841
+ }
1842
+ },
1843
+ "node_modules/lightningcss-linux-arm64-gnu": {
1844
+ "version": "1.32.0",
1845
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
1846
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
1847
+ "cpu": [
1848
+ "arm64"
1849
+ ],
1850
+ "dev": true,
1851
+ "license": "MPL-2.0",
1852
+ "optional": true,
1853
+ "os": [
1854
+ "linux"
1855
+ ],
1856
+ "engines": {
1857
+ "node": ">= 12.0.0"
1858
+ },
1859
+ "funding": {
1860
+ "type": "opencollective",
1861
+ "url": "https://opencollective.com/parcel"
1862
+ }
1863
+ },
1864
+ "node_modules/lightningcss-linux-arm64-musl": {
1865
+ "version": "1.32.0",
1866
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
1867
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
1868
+ "cpu": [
1869
+ "arm64"
1870
+ ],
1871
+ "dev": true,
1872
+ "license": "MPL-2.0",
1873
+ "optional": true,
1874
+ "os": [
1875
+ "linux"
1876
+ ],
1877
+ "engines": {
1878
+ "node": ">= 12.0.0"
1879
+ },
1880
+ "funding": {
1881
+ "type": "opencollective",
1882
+ "url": "https://opencollective.com/parcel"
1883
+ }
1884
+ },
1885
+ "node_modules/lightningcss-linux-x64-gnu": {
1886
+ "version": "1.32.0",
1887
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
1888
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
1889
+ "cpu": [
1890
+ "x64"
1891
+ ],
1892
+ "dev": true,
1893
+ "license": "MPL-2.0",
1894
+ "optional": true,
1895
+ "os": [
1896
+ "linux"
1897
+ ],
1898
+ "engines": {
1899
+ "node": ">= 12.0.0"
1900
+ },
1901
+ "funding": {
1902
+ "type": "opencollective",
1903
+ "url": "https://opencollective.com/parcel"
1904
+ }
1905
+ },
1906
+ "node_modules/lightningcss-linux-x64-musl": {
1907
+ "version": "1.32.0",
1908
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
1909
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
1910
+ "cpu": [
1911
+ "x64"
1912
+ ],
1913
+ "dev": true,
1914
+ "license": "MPL-2.0",
1915
+ "optional": true,
1916
+ "os": [
1917
+ "linux"
1918
+ ],
1919
+ "engines": {
1920
+ "node": ">= 12.0.0"
1921
+ },
1922
+ "funding": {
1923
+ "type": "opencollective",
1924
+ "url": "https://opencollective.com/parcel"
1925
+ }
1926
+ },
1927
+ "node_modules/lightningcss-win32-arm64-msvc": {
1928
+ "version": "1.32.0",
1929
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
1930
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
1931
+ "cpu": [
1932
+ "arm64"
1933
+ ],
1934
+ "dev": true,
1935
+ "license": "MPL-2.0",
1936
+ "optional": true,
1937
+ "os": [
1938
+ "win32"
1939
+ ],
1940
+ "engines": {
1941
+ "node": ">= 12.0.0"
1942
+ },
1943
+ "funding": {
1944
+ "type": "opencollective",
1945
+ "url": "https://opencollective.com/parcel"
1946
+ }
1947
+ },
1948
+ "node_modules/lightningcss-win32-x64-msvc": {
1949
+ "version": "1.32.0",
1950
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
1951
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
1952
+ "cpu": [
1953
+ "x64"
1954
+ ],
1955
+ "dev": true,
1956
+ "license": "MPL-2.0",
1957
+ "optional": true,
1958
+ "os": [
1959
+ "win32"
1960
+ ],
1961
+ "engines": {
1962
+ "node": ">= 12.0.0"
1963
+ },
1964
+ "funding": {
1965
+ "type": "opencollective",
1966
+ "url": "https://opencollective.com/parcel"
1967
+ }
1968
+ },
1969
+ "node_modules/locate-path": {
1970
+ "version": "6.0.0",
1971
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
1972
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
1973
+ "dev": true,
1974
+ "license": "MIT",
1975
+ "dependencies": {
1976
+ "p-locate": "^5.0.0"
1977
+ },
1978
+ "engines": {
1979
+ "node": ">=10"
1980
+ },
1981
+ "funding": {
1982
+ "url": "https://github.com/sponsors/sindresorhus"
1983
+ }
1984
+ },
1985
+ "node_modules/lodash.merge": {
1986
+ "version": "4.6.2",
1987
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
1988
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
1989
+ "dev": true,
1990
+ "license": "MIT"
1991
+ },
1992
+ "node_modules/lru-cache": {
1993
+ "version": "5.1.1",
1994
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1995
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1996
+ "dev": true,
1997
+ "license": "ISC",
1998
+ "dependencies": {
1999
+ "yallist": "^3.0.2"
2000
+ }
2001
+ },
2002
+ "node_modules/minimatch": {
2003
+ "version": "3.1.5",
2004
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
2005
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
2006
+ "dev": true,
2007
+ "license": "ISC",
2008
+ "dependencies": {
2009
+ "brace-expansion": "^1.1.7"
2010
+ },
2011
+ "engines": {
2012
+ "node": "*"
2013
+ }
2014
+ },
2015
+ "node_modules/ms": {
2016
+ "version": "2.1.3",
2017
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2018
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2019
+ "dev": true,
2020
+ "license": "MIT"
2021
+ },
2022
+ "node_modules/nanoid": {
2023
+ "version": "3.3.11",
2024
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
2025
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
2026
+ "dev": true,
2027
+ "funding": [
2028
+ {
2029
+ "type": "github",
2030
+ "url": "https://github.com/sponsors/ai"
2031
+ }
2032
+ ],
2033
+ "license": "MIT",
2034
+ "bin": {
2035
+ "nanoid": "bin/nanoid.cjs"
2036
+ },
2037
+ "engines": {
2038
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
2039
+ }
2040
+ },
2041
+ "node_modules/natural-compare": {
2042
+ "version": "1.4.0",
2043
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
2044
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
2045
+ "dev": true,
2046
+ "license": "MIT"
2047
+ },
2048
+ "node_modules/node-releases": {
2049
+ "version": "2.0.36",
2050
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
2051
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
2052
+ "dev": true,
2053
+ "license": "MIT"
2054
+ },
2055
+ "node_modules/optionator": {
2056
+ "version": "0.9.4",
2057
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
2058
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
2059
+ "dev": true,
2060
+ "license": "MIT",
2061
+ "dependencies": {
2062
+ "deep-is": "^0.1.3",
2063
+ "fast-levenshtein": "^2.0.6",
2064
+ "levn": "^0.4.1",
2065
+ "prelude-ls": "^1.2.1",
2066
+ "type-check": "^0.4.0",
2067
+ "word-wrap": "^1.2.5"
2068
+ },
2069
+ "engines": {
2070
+ "node": ">= 0.8.0"
2071
+ }
2072
+ },
2073
+ "node_modules/p-limit": {
2074
+ "version": "3.1.0",
2075
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
2076
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
2077
+ "dev": true,
2078
+ "license": "MIT",
2079
+ "dependencies": {
2080
+ "yocto-queue": "^0.1.0"
2081
+ },
2082
+ "engines": {
2083
+ "node": ">=10"
2084
+ },
2085
+ "funding": {
2086
+ "url": "https://github.com/sponsors/sindresorhus"
2087
+ }
2088
+ },
2089
+ "node_modules/p-locate": {
2090
+ "version": "5.0.0",
2091
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
2092
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
2093
+ "dev": true,
2094
+ "license": "MIT",
2095
+ "dependencies": {
2096
+ "p-limit": "^3.0.2"
2097
+ },
2098
+ "engines": {
2099
+ "node": ">=10"
2100
+ },
2101
+ "funding": {
2102
+ "url": "https://github.com/sponsors/sindresorhus"
2103
+ }
2104
+ },
2105
+ "node_modules/parent-module": {
2106
+ "version": "1.0.1",
2107
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
2108
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
2109
+ "dev": true,
2110
+ "license": "MIT",
2111
+ "dependencies": {
2112
+ "callsites": "^3.0.0"
2113
+ },
2114
+ "engines": {
2115
+ "node": ">=6"
2116
+ }
2117
+ },
2118
+ "node_modules/path-exists": {
2119
+ "version": "4.0.0",
2120
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
2121
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
2122
+ "dev": true,
2123
+ "license": "MIT",
2124
+ "engines": {
2125
+ "node": ">=8"
2126
+ }
2127
+ },
2128
+ "node_modules/path-key": {
2129
+ "version": "3.1.1",
2130
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
2131
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
2132
+ "dev": true,
2133
+ "license": "MIT",
2134
+ "engines": {
2135
+ "node": ">=8"
2136
+ }
2137
+ },
2138
+ "node_modules/picocolors": {
2139
+ "version": "1.1.1",
2140
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
2141
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
2142
+ "dev": true,
2143
+ "license": "ISC"
2144
+ },
2145
+ "node_modules/picomatch": {
2146
+ "version": "4.0.4",
2147
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
2148
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
2149
+ "dev": true,
2150
+ "license": "MIT",
2151
+ "engines": {
2152
+ "node": ">=12"
2153
+ },
2154
+ "funding": {
2155
+ "url": "https://github.com/sponsors/jonschlinkert"
2156
+ }
2157
+ },
2158
+ "node_modules/postcss": {
2159
+ "version": "8.5.8",
2160
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
2161
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
2162
+ "dev": true,
2163
+ "funding": [
2164
+ {
2165
+ "type": "opencollective",
2166
+ "url": "https://opencollective.com/postcss/"
2167
+ },
2168
+ {
2169
+ "type": "tidelift",
2170
+ "url": "https://tidelift.com/funding/github/npm/postcss"
2171
+ },
2172
+ {
2173
+ "type": "github",
2174
+ "url": "https://github.com/sponsors/ai"
2175
+ }
2176
+ ],
2177
+ "license": "MIT",
2178
+ "dependencies": {
2179
+ "nanoid": "^3.3.11",
2180
+ "picocolors": "^1.1.1",
2181
+ "source-map-js": "^1.2.1"
2182
+ },
2183
+ "engines": {
2184
+ "node": "^10 || ^12 || >=14"
2185
+ }
2186
+ },
2187
+ "node_modules/prelude-ls": {
2188
+ "version": "1.2.1",
2189
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
2190
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
2191
+ "dev": true,
2192
+ "license": "MIT",
2193
+ "engines": {
2194
+ "node": ">= 0.8.0"
2195
+ }
2196
+ },
2197
+ "node_modules/punycode": {
2198
+ "version": "2.3.1",
2199
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
2200
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
2201
+ "dev": true,
2202
+ "license": "MIT",
2203
+ "engines": {
2204
+ "node": ">=6"
2205
+ }
2206
+ },
2207
+ "node_modules/react": {
2208
+ "version": "19.2.4",
2209
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
2210
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
2211
+ "license": "MIT",
2212
+ "engines": {
2213
+ "node": ">=0.10.0"
2214
+ }
2215
+ },
2216
+ "node_modules/react-dom": {
2217
+ "version": "19.2.4",
2218
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
2219
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
2220
+ "license": "MIT",
2221
+ "dependencies": {
2222
+ "scheduler": "^0.27.0"
2223
+ },
2224
+ "peerDependencies": {
2225
+ "react": "^19.2.4"
2226
+ }
2227
+ },
2228
+ "node_modules/react-router": {
2229
+ "version": "7.13.2",
2230
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz",
2231
+ "integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==",
2232
+ "license": "MIT",
2233
+ "dependencies": {
2234
+ "cookie": "^1.0.1",
2235
+ "set-cookie-parser": "^2.6.0"
2236
+ },
2237
+ "engines": {
2238
+ "node": ">=20.0.0"
2239
+ },
2240
+ "peerDependencies": {
2241
+ "react": ">=18",
2242
+ "react-dom": ">=18"
2243
+ },
2244
+ "peerDependenciesMeta": {
2245
+ "react-dom": {
2246
+ "optional": true
2247
+ }
2248
+ }
2249
+ },
2250
+ "node_modules/react-router-dom": {
2251
+ "version": "7.13.2",
2252
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.2.tgz",
2253
+ "integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==",
2254
+ "license": "MIT",
2255
+ "dependencies": {
2256
+ "react-router": "7.13.2"
2257
+ },
2258
+ "engines": {
2259
+ "node": ">=20.0.0"
2260
+ },
2261
+ "peerDependencies": {
2262
+ "react": ">=18",
2263
+ "react-dom": ">=18"
2264
+ }
2265
+ },
2266
+ "node_modules/resolve-from": {
2267
+ "version": "4.0.0",
2268
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
2269
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
2270
+ "dev": true,
2271
+ "license": "MIT",
2272
+ "engines": {
2273
+ "node": ">=4"
2274
+ }
2275
+ },
2276
+ "node_modules/rolldown": {
2277
+ "version": "1.0.0-rc.12",
2278
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
2279
+ "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
2280
+ "dev": true,
2281
+ "license": "MIT",
2282
+ "dependencies": {
2283
+ "@oxc-project/types": "=0.122.0",
2284
+ "@rolldown/pluginutils": "1.0.0-rc.12"
2285
+ },
2286
+ "bin": {
2287
+ "rolldown": "bin/cli.mjs"
2288
+ },
2289
+ "engines": {
2290
+ "node": "^20.19.0 || >=22.12.0"
2291
+ },
2292
+ "optionalDependencies": {
2293
+ "@rolldown/binding-android-arm64": "1.0.0-rc.12",
2294
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
2295
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.12",
2296
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
2297
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
2298
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
2299
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
2300
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
2301
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
2302
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
2303
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
2304
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
2305
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
2306
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
2307
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
2308
+ }
2309
+ },
2310
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
2311
+ "version": "1.0.0-rc.12",
2312
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
2313
+ "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
2314
+ "dev": true,
2315
+ "license": "MIT"
2316
+ },
2317
+ "node_modules/scheduler": {
2318
+ "version": "0.27.0",
2319
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
2320
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
2321
+ "license": "MIT"
2322
+ },
2323
+ "node_modules/semver": {
2324
+ "version": "6.3.1",
2325
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
2326
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
2327
+ "dev": true,
2328
+ "license": "ISC",
2329
+ "bin": {
2330
+ "semver": "bin/semver.js"
2331
+ }
2332
+ },
2333
+ "node_modules/set-cookie-parser": {
2334
+ "version": "2.7.2",
2335
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
2336
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
2337
+ "license": "MIT"
2338
+ },
2339
+ "node_modules/shebang-command": {
2340
+ "version": "2.0.0",
2341
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
2342
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
2343
+ "dev": true,
2344
+ "license": "MIT",
2345
+ "dependencies": {
2346
+ "shebang-regex": "^3.0.0"
2347
+ },
2348
+ "engines": {
2349
+ "node": ">=8"
2350
+ }
2351
+ },
2352
+ "node_modules/shebang-regex": {
2353
+ "version": "3.0.0",
2354
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
2355
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
2356
+ "dev": true,
2357
+ "license": "MIT",
2358
+ "engines": {
2359
+ "node": ">=8"
2360
+ }
2361
+ },
2362
+ "node_modules/source-map-js": {
2363
+ "version": "1.2.1",
2364
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
2365
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
2366
+ "dev": true,
2367
+ "license": "BSD-3-Clause",
2368
+ "engines": {
2369
+ "node": ">=0.10.0"
2370
+ }
2371
+ },
2372
+ "node_modules/strip-json-comments": {
2373
+ "version": "3.1.1",
2374
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
2375
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
2376
+ "dev": true,
2377
+ "license": "MIT",
2378
+ "engines": {
2379
+ "node": ">=8"
2380
+ },
2381
+ "funding": {
2382
+ "url": "https://github.com/sponsors/sindresorhus"
2383
+ }
2384
+ },
2385
+ "node_modules/supports-color": {
2386
+ "version": "7.2.0",
2387
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
2388
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
2389
+ "dev": true,
2390
+ "license": "MIT",
2391
+ "dependencies": {
2392
+ "has-flag": "^4.0.0"
2393
+ },
2394
+ "engines": {
2395
+ "node": ">=8"
2396
+ }
2397
+ },
2398
+ "node_modules/tinyglobby": {
2399
+ "version": "0.2.15",
2400
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
2401
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
2402
+ "dev": true,
2403
+ "license": "MIT",
2404
+ "dependencies": {
2405
+ "fdir": "^6.5.0",
2406
+ "picomatch": "^4.0.3"
2407
+ },
2408
+ "engines": {
2409
+ "node": ">=12.0.0"
2410
+ },
2411
+ "funding": {
2412
+ "url": "https://github.com/sponsors/SuperchupuDev"
2413
+ }
2414
+ },
2415
+ "node_modules/tslib": {
2416
+ "version": "2.8.1",
2417
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
2418
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
2419
+ "dev": true,
2420
+ "license": "0BSD",
2421
+ "optional": true
2422
+ },
2423
+ "node_modules/type-check": {
2424
+ "version": "0.4.0",
2425
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
2426
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
2427
+ "dev": true,
2428
+ "license": "MIT",
2429
+ "dependencies": {
2430
+ "prelude-ls": "^1.2.1"
2431
+ },
2432
+ "engines": {
2433
+ "node": ">= 0.8.0"
2434
+ }
2435
+ },
2436
+ "node_modules/update-browserslist-db": {
2437
+ "version": "1.2.3",
2438
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
2439
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
2440
+ "dev": true,
2441
+ "funding": [
2442
+ {
2443
+ "type": "opencollective",
2444
+ "url": "https://opencollective.com/browserslist"
2445
+ },
2446
+ {
2447
+ "type": "tidelift",
2448
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
2449
+ },
2450
+ {
2451
+ "type": "github",
2452
+ "url": "https://github.com/sponsors/ai"
2453
+ }
2454
+ ],
2455
+ "license": "MIT",
2456
+ "dependencies": {
2457
+ "escalade": "^3.2.0",
2458
+ "picocolors": "^1.1.1"
2459
+ },
2460
+ "bin": {
2461
+ "update-browserslist-db": "cli.js"
2462
+ },
2463
+ "peerDependencies": {
2464
+ "browserslist": ">= 4.21.0"
2465
+ }
2466
+ },
2467
+ "node_modules/uri-js": {
2468
+ "version": "4.4.1",
2469
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
2470
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
2471
+ "dev": true,
2472
+ "license": "BSD-2-Clause",
2473
+ "dependencies": {
2474
+ "punycode": "^2.1.0"
2475
+ }
2476
+ },
2477
+ "node_modules/vite": {
2478
+ "version": "8.0.3",
2479
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
2480
+ "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
2481
+ "dev": true,
2482
+ "license": "MIT",
2483
+ "dependencies": {
2484
+ "lightningcss": "^1.32.0",
2485
+ "picomatch": "^4.0.4",
2486
+ "postcss": "^8.5.8",
2487
+ "rolldown": "1.0.0-rc.12",
2488
+ "tinyglobby": "^0.2.15"
2489
+ },
2490
+ "bin": {
2491
+ "vite": "bin/vite.js"
2492
+ },
2493
+ "engines": {
2494
+ "node": "^20.19.0 || >=22.12.0"
2495
+ },
2496
+ "funding": {
2497
+ "url": "https://github.com/vitejs/vite?sponsor=1"
2498
+ },
2499
+ "optionalDependencies": {
2500
+ "fsevents": "~2.3.3"
2501
+ },
2502
+ "peerDependencies": {
2503
+ "@types/node": "^20.19.0 || >=22.12.0",
2504
+ "@vitejs/devtools": "^0.1.0",
2505
+ "esbuild": "^0.27.0",
2506
+ "jiti": ">=1.21.0",
2507
+ "less": "^4.0.0",
2508
+ "sass": "^1.70.0",
2509
+ "sass-embedded": "^1.70.0",
2510
+ "stylus": ">=0.54.8",
2511
+ "sugarss": "^5.0.0",
2512
+ "terser": "^5.16.0",
2513
+ "tsx": "^4.8.1",
2514
+ "yaml": "^2.4.2"
2515
+ },
2516
+ "peerDependenciesMeta": {
2517
+ "@types/node": {
2518
+ "optional": true
2519
+ },
2520
+ "@vitejs/devtools": {
2521
+ "optional": true
2522
+ },
2523
+ "esbuild": {
2524
+ "optional": true
2525
+ },
2526
+ "jiti": {
2527
+ "optional": true
2528
+ },
2529
+ "less": {
2530
+ "optional": true
2531
+ },
2532
+ "sass": {
2533
+ "optional": true
2534
+ },
2535
+ "sass-embedded": {
2536
+ "optional": true
2537
+ },
2538
+ "stylus": {
2539
+ "optional": true
2540
+ },
2541
+ "sugarss": {
2542
+ "optional": true
2543
+ },
2544
+ "terser": {
2545
+ "optional": true
2546
+ },
2547
+ "tsx": {
2548
+ "optional": true
2549
+ },
2550
+ "yaml": {
2551
+ "optional": true
2552
+ }
2553
+ }
2554
+ },
2555
+ "node_modules/which": {
2556
+ "version": "2.0.2",
2557
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
2558
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
2559
+ "dev": true,
2560
+ "license": "ISC",
2561
+ "dependencies": {
2562
+ "isexe": "^2.0.0"
2563
+ },
2564
+ "bin": {
2565
+ "node-which": "bin/node-which"
2566
+ },
2567
+ "engines": {
2568
+ "node": ">= 8"
2569
+ }
2570
+ },
2571
+ "node_modules/word-wrap": {
2572
+ "version": "1.2.5",
2573
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
2574
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
2575
+ "dev": true,
2576
+ "license": "MIT",
2577
+ "engines": {
2578
+ "node": ">=0.10.0"
2579
+ }
2580
+ },
2581
+ "node_modules/yallist": {
2582
+ "version": "3.1.1",
2583
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
2584
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
2585
+ "dev": true,
2586
+ "license": "ISC"
2587
+ },
2588
+ "node_modules/yocto-queue": {
2589
+ "version": "0.1.0",
2590
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
2591
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
2592
+ "dev": true,
2593
+ "license": "MIT",
2594
+ "engines": {
2595
+ "node": ">=10"
2596
+ },
2597
+ "funding": {
2598
+ "url": "https://github.com/sponsors/sindresorhus"
2599
+ }
2600
+ },
2601
+ "node_modules/zod": {
2602
+ "version": "4.3.6",
2603
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
2604
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
2605
+ "dev": true,
2606
+ "license": "MIT",
2607
+ "funding": {
2608
+ "url": "https://github.com/sponsors/colinhacks"
2609
+ }
2610
+ },
2611
+ "node_modules/zod-validation-error": {
2612
+ "version": "4.0.2",
2613
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
2614
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
2615
+ "dev": true,
2616
+ "license": "MIT",
2617
+ "engines": {
2618
+ "node": ">=18.0.0"
2619
+ },
2620
+ "peerDependencies": {
2621
+ "zod": "^3.25.0 || ^4.0.0"
2622
+ }
2623
+ }
2624
+ }
2625
+ }
frontend/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^19.2.4",
14
+ "react-dom": "^19.2.4",
15
+ "react-router-dom": "^7.13.2"
16
+ },
17
+ "devDependencies": {
18
+ "@eslint/js": "^9.39.4",
19
+ "@types/react": "^19.2.14",
20
+ "@types/react-dom": "^19.2.3",
21
+ "@vitejs/plugin-react": "^6.0.1",
22
+ "eslint": "^9.39.4",
23
+ "eslint-plugin-react-hooks": "^7.0.1",
24
+ "eslint-plugin-react-refresh": "^0.5.2",
25
+ "globals": "^17.4.0",
26
+ "vite": "^8.0.1"
27
+ }
28
+ }
frontend/public/favicon.svg ADDED
frontend/public/icons.svg ADDED
frontend/src/App.css ADDED
@@ -0,0 +1 @@
 
 
1
+ /* Minimal App-level overrides (global styles in index.css) */
frontend/src/App.jsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { BrowserRouter as Router, Routes, Route, NavLink } from 'react-router-dom';
2
+ import HomePage from './pages/HomePage';
3
+ import PredictPage from './pages/PredictPage';
4
+ import BreedExplorerPage from './pages/BreedExplorerPage';
5
+ import AboutPage from './pages/AboutPage';
6
+ import './App.css';
7
+
8
+ function App() {
9
+ return (
10
+ <Router>
11
+ <nav className="navbar">
12
+ <div className="navbar-inner">
13
+ <NavLink to="/" className="navbar-brand">
14
+ 🐄 <span>CattleAI</span>
15
+ </NavLink>
16
+ <ul className="nav-links">
17
+ <li><NavLink to="/" end>Home</NavLink></li>
18
+ <li><NavLink to="/predict">Predict</NavLink></li>
19
+ <li><NavLink to="/breeds">Breeds</NavLink></li>
20
+ <li><NavLink to="/about">About</NavLink></li>
21
+ </ul>
22
+ </div>
23
+ </nav>
24
+
25
+ <Routes>
26
+ <Route path="/" element={<HomePage />} />
27
+ <Route path="/predict" element={<PredictPage />} />
28
+ <Route path="/breeds" element={<BreedExplorerPage />} />
29
+ <Route path="/about" element={<AboutPage />} />
30
+ </Routes>
31
+
32
+ <footer className="footer">
33
+ <p>
34
+ Built by Ajaya and team
35
+ {' '} · Powered by PyTorch & FastAPI · {new Date().getFullYear()}
36
+ </p>
37
+ </footer>
38
+ </Router>
39
+ );
40
+ }
41
+
42
+ export default App;