diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..7d9e5cc6cef12fe9bbd5853550fdf5f3a09a5bd0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,53 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1_perspective.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_blur.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_brightness.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_combined.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_rotate.png filter=lfs diff=lfs merge=lfs -text +backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1.png filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Black.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Bold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-ExtraBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-ExtraLight.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Light.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Medium.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Regular.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-SemiBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_Condensed-Thin.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Black.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Bold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-ExtraBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-ExtraLight.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Light.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Medium.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Regular.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-SemiBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_ExtraCondensed-Thin.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Black.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Bold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-ExtraBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-ExtraLight.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Light.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Medium.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Regular.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-SemiBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya_SemiCondensed-Thin.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Black[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Black.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Bold[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Bold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-ExtraBold[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-ExtraBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-ExtraLight[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-ExtraLight.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Light[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Light.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Medium[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Medium.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Regular.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-SemiBold.ttf filter=lfs diff=lfs merge=lfs -text +content/static/NotoSansOriya-Thin.ttf filter=lfs diff=lfs merge=lfs -text +frontend/public/logo.png filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..146d80822502dffb631d3b4dfce8122c163825e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Multi-stage build for Hugging Face Spaces (Docker) with single URL serving + +# --- Frontend build stage --- +FROM node:20-alpine AS frontend-build +WORKDIR /app +COPY frontend/package*.json ./ +RUN npm ci +COPY frontend ./ +RUN npm run build + +# --- Backend stage --- +FROM python:3.11-slim AS runtime + +# System deps for OpenCV and general libs +RUN apt-get update && apt-get install -y --no-install-recommends \ + libgl1 \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender1 \ + build-essential \ + python3-dev \ + git \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install backend deps +COPY backend/requirements.txt /app/backend/requirements.txt +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r /app/backend/requirements.txt + +# Copy application code +COPY backend /app/backend +COPY content /app/content + +# Copy built frontend +COPY --from=frontend-build /app/dist /app/frontend_dist + +# Environment +ENV PORT=7860 \ + DATA_DIR=/data \ + FRONTEND_DIST=/app/frontend_dist + +# Ensure data dir exists with permissive permissions (in case volume isn't mounted yet) +RUN mkdir -p /data && chmod -R 777 /data + +EXPOSE 7860 + +# Run FastAPI (serve API + static frontend) +CMD ["python", "-m", "uvicorn", "app.main:app", "--app-dir", "backend", "--host", "0.0.0.0", "--port", "7860"] diff --git a/README.md b/README.md index a9832434b260f4915c83303945ee9929be580ffe..bda7457003acf824df0e1f41f660c7bfcad4993a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,169 @@ --- -title: OCR Annotation -emoji: 📊 -colorFrom: gray -colorTo: gray +title: Odia OCR Annotation + Synthetic Generator +emoji: 🧩 +colorFrom: indigo +colorTo: yellow sdk: docker +sdk_version: "1.0.0" pinned: false -short_description: OCR-annotation --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +# Odia OCR Annotation + Synthetic Text Generator + +A unified repository that provides: +- An OCR annotation tool (React frontend + FastAPI backend) to upload images, run OCR via Gemini, edit validated text, and export CSVs. +- A synthetic text generator (exposed via backend API) to render Odia/Sanskrit-like text with realistic paper/effects, including HuggingFace dataset processing. + +## Repository Structure + +- `backend/` + - `app/main.py`: FastAPI app with two routers: `/api/ocr` and `/api/synthetic` + - `app/api/routers/ocr.py`: OCR endpoints (upload, OCR, annotations import/export) + - `app/api/routers/synthetic.py`: Synthetic generation endpoints + - `app/services/`: Shared services + - `ocr_processor.py`: Gemini OCR + - `annotations.py`: CSV/JSON I/O + - `synthetic/`: generator modules (config, core, effects, backgrounds, text_renderer, transformations, huggingface_processor) + - `data/`: runtime storage + - `uploaded_images/`: uploaded images (served at `/images`) + - `annotations/`: `annotations.csv` and JSON + - `synth_outputs/`: generated images and CSVs (served at `/static/synthetic`) + - `requirements.txt`: backend dependencies +- `frontend/` + - Vite + React + Tailwind app + - Routes: `/ocr` (annotation UI) and `/synthetic` (generator UI) +- `content/static/`: NotoSans Oriya fonts used by generator + +## Run Locally + +1) Backend +- `pip install -r backend/requirements.txt` +- From `backend/`: `uvicorn app.main:app --reload` +- Static mounts: + - `/images` → `backend/data/uploaded_images` + - `/static/synthetic` → `backend/data/synth_outputs` + +2) Frontend +- `cd frontend && npm install && npm run dev` +- Open `http://localhost:5173` +- Use navigation to switch between OCR and Synthetic pages + +## OCR API (FastAPI) + +- `POST /api/ocr/upload`: + - Multipart files field: `files` + - Stores images in `backend/data/uploaded_images` +- `POST /api/ocr/process`: + - JSON: `{ "api_key": "", "image_filenames": ["img1.png", ...] }` + - Returns: `{ "img1.png": "extracted text", ... }` +- `GET /api/ocr/annotations`: + - Returns current annotations, valid/missing images +- `POST /api/ocr/save`: + - JSON: `{ "": { "extracted_text": "...", "validated_text": "..." } }` + - Saves to CSV and JSON in `backend/data/annotations` +- `POST /api/ocr/import`: + - Multipart: `file` (CSV), `image_folder` (e.g., `uploaded_images`) + - Validates and returns annotations + image presence +- `POST /api/ocr/export`: + - JSON: `{ annotations: {...}, validated_texts: {...} }` + - Returns a downloadable CSV + +Note: Legacy endpoints (`/upload/`, `/process-ocr/`, etc.) are temporarily supported for the older UI. Prefer `/api/ocr/...` going forward. + +## Synthetic API (FastAPI) + +- `POST /api/synthetic/generate` + - Modes: `single`, `comprehensive`, `ultra-realistic`, `huggingface` + - Request body examples: + - Non-HF: + `{ "mode": "single", "text": "some Odia text", "output_subdir": "demo_run_01" }` + - HF CSV: + `{ "mode": "huggingface", "dataset_url": "https://.../data.csv", "text_column": "text", "max_samples": 100, "output_subdir": "hf_demo" }` + - Response: + - Non-HF: `{ "status": "ok", "output_dir": "/static/synthetic/" }` + - HF: `{ "status": "ok", "output_dir": "/static/synthetic/", "csv": "/static/synthetic//dataset.csv", "images_dir": "/static/synthetic//images" }` + - Outputs are stored under `backend/data/synth_outputs//` and publicly served at `/static/synthetic//...`. + +## Fonts + +- Generator uses fonts from `content/static/`. +- Default: `NotoSansOriya_Condensed-Regular.ttf` (configurable). Ensure the directory exists. + +## Effects & Styles + +- Paper styles: lined paper, old paper, birch, parchment +- Effects: rotation, brightness/contrast/noise/blur, fold/crease, ink bleed, perspective, shadows, morphological ops, scanner artifacts, lens distortion, washboard/cylinder warps + +## Notes + +- The backend expects the Gemini API key to be provided per-request to `/api/ocr/process`. Do not hardcode keys server-side. +- For HuggingFace datasets, the backend uses `datasets` when possible, or downloads raw CSV URLs. +- You can browse generated outputs via the links returned by `/api/synthetic/generate`. + +## Deploy to Hugging Face Spaces (Docker) + +This repo includes a multi-stage Dockerfile to deploy both backend and the built frontend as a single Space. + +Steps: +- Create a new Space → Type: Docker +- Push this repository to the Space +- In Space Settings: + - Enable Persistent Storage + - (Optional) Add Secrets/Env Vars as needed, e.g., `DATA_DIR=/data` (default already) and `FRONTEND_DIST=/app/frontend_dist` +- The container exposes port `7860` by default. + +What the image does: +- Builds the frontend (`frontend/`) and copies the `dist/` to `/app/frontend_dist` +- Installs backend dependencies and runs `uvicorn app.main:app` from `backend/` +- Serves: + - API at `/api/...` + - Uploaded images at `/images` + - Synthetic outputs at `/static/synthetic` + - Frontend SPA at `/` (served from `/app/frontend_dist`) + + +1. **Paper Textures**: Realistic paper fiber patterns using Perlin noise +2. **Aging Effects**: Edge darkening and aging patterns +3. **Physical Damage**: Fold lines, creases, and ink bleeding +4. **Scanner Artifacts**: Dust, compression artifacts, scanning lines +5. **Geometric Distortions**: Perspective changes, cylindrical warping +6. **Lighting Effects**: Shadows and lens distortions + +## Font Requirements + +The generator requires appropriate fonts for text rendering. Default configuration expects: +- Font directory: `/content/static/` +- Font file: `NotoSansOriya_ExtraCondensed-Regular.ttf` + +You can specify custom fonts using `--font-dir` and `--font` parameters. + +## Performance Tips + +- Use `--max-samples` to limit processing for large datasets +- Disable advanced effects with `--no-advanced-effects` for faster generation +- Use multiprocessing with `--use-multiprocessing` for batch jobs +- Adjust image dimensions to balance quality and speed + +## Error Handling + +The package includes comprehensive error handling: +- Graceful fallbacks for missing dependencies +- Detailed logging for debugging +- Validation of input parameters +- Safe handling of malformed datasets + +## Contributing + +The modular structure makes it easy to extend: +- Add new effects in `effects.py` +- Implement new background styles in `backgrounds.py` +- Create custom transformations in `transformations.py` +- Extend dataset processing in `huggingface_processor.py` + +## License + +[Add your license information here] + +--- + +**Note**: This is a complete rewrite of the original monolithic code into a modular, extensible package with added HuggingFace dataset processing capabilities. diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..423fd0daeca324e52a7df13f8ab1a2f808572d0d --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1,2 @@ +# Makes 'app' a package + diff --git a/backend/app/__pycache__/__init__.cpython-313.pyc b/backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..caf4aaddf00965447f5343f552b9ce145132d6ac Binary files /dev/null and b/backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..26c9e8ad8d9c0e87eeab05b305cd8c1b75dd01a7 --- /dev/null +++ b/backend/app/api/__init__.py @@ -0,0 +1,2 @@ +# api package + diff --git a/backend/app/api/__pycache__/__init__.cpython-313.pyc b/backend/app/api/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05af20f181734b5ba32154776e94317680b8a1ff Binary files /dev/null and b/backend/app/api/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/api/routers/__init__.py b/backend/app/api/routers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8da267f8c90db49020b7b7a37a76d4504524e564 --- /dev/null +++ b/backend/app/api/routers/__init__.py @@ -0,0 +1,2 @@ +# routers package + diff --git a/backend/app/api/routers/__pycache__/__init__.cpython-313.pyc b/backend/app/api/routers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6214b7cc7a8756c5d657bfbcf27d2218bcc1728 Binary files /dev/null and b/backend/app/api/routers/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/api/routers/__pycache__/ocr.cpython-313.pyc b/backend/app/api/routers/__pycache__/ocr.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc3cb6d745e2f2bf041df439a43bf5f6a0ba5712 Binary files /dev/null and b/backend/app/api/routers/__pycache__/ocr.cpython-313.pyc differ diff --git a/backend/app/api/routers/__pycache__/synthetic.cpython-313.pyc b/backend/app/api/routers/__pycache__/synthetic.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d41cc68bbcbf4899dfa297a9b1d0dd98f1a5e151 Binary files /dev/null and b/backend/app/api/routers/__pycache__/synthetic.cpython-313.pyc differ diff --git a/backend/app/api/routers/ocr.py b/backend/app/api/routers/ocr.py new file mode 100644 index 0000000000000000000000000000000000000000..f0f2b791445b1d674b51fe4791433e949845189e --- /dev/null +++ b/backend/app/api/routers/ocr.py @@ -0,0 +1,166 @@ +from fastapi import APIRouter, UploadFile, File, Form, HTTPException +from fastapi.responses import FileResponse +from typing import List, Dict +import os +import shutil +import re +import unicodedata + +from ...services.annotations import ( + load_annotations_from_csv, + save_annotations_to_csv, + save_annotations, +) +from ...services.ocr_processor import batch_run_ocr + + +router = APIRouter(prefix="/api/ocr", tags=["ocr"]) + + +SUPPORTED_IMAGE_TYPES = {"jpg", "jpeg", "png", "bmp", "webp", "tiff"} + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +DEFAULT_DATA_DIR = os.path.join(BASE_DIR, "data") +DATA_DIR = os.getenv("DATA_DIR", DEFAULT_DATA_DIR) +UPLOAD_DIR = os.path.join(DATA_DIR, "uploaded_images") +ANNOTATIONS_DIR = os.path.join(DATA_DIR, "annotations") +ANNOTATION_CSV_PATH = os.path.join(ANNOTATIONS_DIR, "annotations.csv") + +os.makedirs(UPLOAD_DIR, exist_ok=True) +os.makedirs(ANNOTATIONS_DIR, exist_ok=True) + +# Ensure CSV exists +if not os.path.exists(ANNOTATION_CSV_PATH): + with open(ANNOTATION_CSV_PATH, 'w', encoding='utf-8-sig') as f: + f.write('image_filename,extracted_text,validated_text\n') + + +@router.post("/upload") +async def upload_images(files: List[UploadFile] = File(...)): + image_names: List[str] = [] + for file in files: + ext = file.filename.split('.')[-1].lower() + if ext not in SUPPORTED_IMAGE_TYPES: + continue + + original_name = os.path.basename(file.filename) + safe_name = sanitize_filename(original_name) + path = os.path.join(UPLOAD_DIR, safe_name) + + # Avoid collisions + counter = 1 + base, ext_with_dot = os.path.splitext(safe_name) + while os.path.exists(path): + candidate = f"{base}_{counter}{ext_with_dot}" + path = os.path.join(UPLOAD_DIR, candidate) + safe_name = candidate + counter += 1 + print("Saving upload to:", path) + with open(path, "wb") as f: + f.write(await file.read()) + image_names.append(safe_name) + return {"status": "success", "images": image_names} + + +@router.post("/process") +def process_ocr(request: Dict[str, object]): + api_key = str(request.get("api_key", "")) + image_filenames = list(request.get("image_filenames", [])) + if not api_key: + raise HTTPException(status_code=400, detail="api_key is required") + if not image_filenames: + raise HTTPException(status_code=400, detail="image_filenames is required") + results = batch_run_ocr(image_filenames, UPLOAD_DIR, api_key) + return results + + +@router.get("/annotations") +def get_annotations(): + try: + annotations, valid_images, missing_images = load_annotations_from_csv(ANNOTATION_CSV_PATH, UPLOAD_DIR) + return { + "annotations": annotations, + "valid_images": valid_images, + "missing_images": missing_images + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/save") +def save_annotated(data: dict): + try: + save_annotations(ANNOTATION_CSV_PATH, data) + return {"status": "saved"} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/import") +async def import_csv(file: UploadFile = File(...), image_folder: str = Form("uploaded_images")): + temp_dir = os.path.join(DATA_DIR, "temp") + os.makedirs(temp_dir, exist_ok=True) + temp_path = os.path.join(temp_dir, os.path.basename(file.filename)) + + with open(temp_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + try: + # If relative folder, resolve within DATA_DIR + folder = image_folder + if not os.path.isabs(folder): + folder = os.path.join(DATA_DIR, folder) + annotations, valid_images, missing_images = load_annotations_from_csv(temp_path, folder) + return { + "annotations": annotations, + "valid_images": valid_images, + "missing_images": missing_images + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/export") +async def export_csv(request: Dict[str, dict]): + try: + annotations = request.get("annotations", {}) + validated_texts = request.get("validated_texts", {}) + + combined_data: Dict[str, Dict[str, str]] = {} + for image_name in annotations.keys(): + combined_data[image_name] = { + "extracted_text": annotations[image_name], + "validated_text": validated_texts.get(image_name, "") + } + + save_annotations_to_csv(ANNOTATION_CSV_PATH, combined_data) + return FileResponse( + ANNOTATION_CSV_PATH, + media_type='text/csv', + filename='annotations.csv' + ) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +def sanitize_filename(name: str) -> str: + """Normalize and sanitize a filename for safe cross-platform serving. + + - Unicode normalize (NFKC) + - Replace whitespace with underscore + - Remove characters not in [A-Za-z0-9._-] + - Collapse multiple underscores + """ + # Normalize + name = unicodedata.normalize("NFKC", name) + base, ext = os.path.splitext(name) + base = re.sub(r"\s+", "_", base) + base = re.sub(r"[^A-Za-z0-9._-]", "_", base) + base = re.sub(r"_+", "_", base).strip("._") + if not base: + base = "file" + ext = "." + ext.lstrip(".").lower() + ext = re.sub(r"[^A-Za-z0-9]", "", ext) + ext = f".{ext}" if ext else "" + return base + ext +# Legacy routes removed after frontend migration to /api/ocr diff --git a/backend/app/api/routers/synthetic.py b/backend/app/api/routers/synthetic.py new file mode 100644 index 0000000000000000000000000000000000000000..a6457ec1b2b83c9a2f182c7a02d89fd4f70071ba --- /dev/null +++ b/backend/app/api/routers/synthetic.py @@ -0,0 +1,151 @@ +from fastapi import APIRouter, HTTPException, Query +from typing import Dict, Optional +import os +import uuid + +from ...services.synthetic.config import ENHANCED_DEFAULT_PARAMS +from ...services.synthetic.core import ( + generate_enhanced_sanskrit_samples, + generate_comprehensive_dataset, + generate_ultra_realistic_samples, +) +from ...services.synthetic.huggingface_processor import HuggingFaceDatasetProcessor + + +router = APIRouter(prefix="/api/synthetic", tags=["synthetic"]) + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +DEFAULT_DATA_DIR = os.path.join(BASE_DIR, "data") +DATA_DIR = os.getenv("DATA_DIR", DEFAULT_DATA_DIR) +SYN_OUT_DIR = os.path.join(DATA_DIR, "synth_outputs") +FONTS_DIR = os.path.abspath(os.path.join(BASE_DIR, os.pardir, os.pardir, "content", "static")) + +os.makedirs(SYN_OUT_DIR, exist_ok=True) + + +def normalized_params(incoming: Optional[Dict]) -> Dict: + params = ENHANCED_DEFAULT_PARAMS.copy() + if incoming: + # convert hyphen keys to underscore if any + normalized = {k.replace('-', '_'): v for k, v in incoming.items()} + params.update(normalized) + # Force font_dir to repo fonts absolute path for backend execution + params['font_dir'] = FONTS_DIR + return params + + +def _list_image_files(base_dir: str, job_id: str) -> Dict: + """Return relative URLs for images under the job output directory.""" + rels = [] + job_dir = os.path.join(base_dir, job_id) + if not os.path.isdir(job_dir): + return {"files": [], "count": 0} + for root, _, files in os.walk(job_dir): + for fn in files: + if fn.lower().endswith((".png", ".jpg", ".jpeg", ".webp")): + abs_path = os.path.join(root, fn) + rel_path = os.path.relpath(abs_path, base_dir) + rels.append(f"/static/synthetic/{rel_path.replace(os.sep, '/')}") + rels.sort() + return {"files": rels, "count": len(rels)} + + +@router.post("/generate") +def generate(request: Dict[str, object]): + """ + Universal generation endpoint. + Body: + { + mode: 'single' | 'comprehensive' | 'ultra-realistic' | 'huggingface', + text?: string (for non-HF modes), + output_subdir?: string, + params?: object, + text_column?: string (HF), + max_samples?: int (HF), + dataset_url?: string (HF) OR csv_file?: string (server-side path) + } + Returns paths relative to /static/synthetic when applicable. + """ + mode = str(request.get("mode", "single")) + text = str(request.get("text", "")).strip() or "କବି ସମ୍ରାଟ ଉପେନ୍ଦ୍ର ଭଞ୍ଜ ..." + output_subdir = str(request.get("output_subdir", "")) + params = normalized_params(request.get("params")) + + # Resolve output dir under synth_outputs + job_id = output_subdir or str(uuid.uuid4()) + out_dir = os.path.join(SYN_OUT_DIR, job_id) + os.makedirs(out_dir, exist_ok=True) + + try: + if mode == 'single': + generate_enhanced_sanskrit_samples( + text=text, + font_path=os.path.join(params['font_dir'], params['font']), + output_dir=out_dir, + params=params, + ) + listing = _list_image_files(SYN_OUT_DIR, job_id) + return {"status": "ok", "output_dir": f"/static/synthetic/{job_id}", **listing} + + elif mode == 'comprehensive': + generate_comprehensive_dataset( + text=text, + output_dir=out_dir, + params=params, + ) + listing = _list_image_files(SYN_OUT_DIR, job_id) + return {"status": "ok", "output_dir": f"/static/synthetic/{job_id}", **listing} + + elif mode == 'ultra-realistic': + generate_ultra_realistic_samples( + text=text, + output_dir=out_dir, + style_focus=request.get("style_focus"), + params=params, + ) + listing = _list_image_files(SYN_OUT_DIR, job_id) + return {"status": "ok", "output_dir": f"/static/synthetic/{job_id}", **listing} + + elif mode == 'huggingface': + text_column = str(request.get("text_column", "text")) + max_samples = request.get("max_samples") + dataset_url = request.get("dataset_url") + csv_file = request.get("csv_file") + + processor = HuggingFaceDatasetProcessor(output_dir=out_dir, params=params) + + if csv_file: + ok = processor.process_local_csv(csv_path=csv_file, text_column=text_column, max_samples=max_samples) + elif dataset_url: + ok = processor.process_huggingface_dataset(dataset_identifier=dataset_url, text_column=text_column, max_samples=max_samples) + else: + raise HTTPException(status_code=400, detail="Provide dataset_url or csv_file for huggingface mode") + + if not ok: + raise HTTPException(status_code=500, detail="HuggingFace processing failed") + + listing = _list_image_files(SYN_OUT_DIR, job_id) + return { + "status": "ok", + "output_dir": f"/static/synthetic/{job_id}", + "csv": f"/static/synthetic/{job_id}/dataset.csv", + "images_dir": f"/static/synthetic/{job_id}/images", + **listing, + } + + else: + raise HTTPException(status_code=400, detail=f"Unknown mode: {mode}") + + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/list") +def list_outputs(job_id: str = Query(..., description="Job ID (output_subdir) to list")): + listing = _list_image_files(SYN_OUT_DIR, job_id) + if listing["count"] == 0: + raise HTTPException(status_code=404, detail="Job not found or contains no images") + return {"status": "ok", "job_id": job_id, **listing} diff --git a/backend/app/data/.gitkeep b/backend/app/data/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/backend/app/data/annotations/annotations.csv b/backend/app/data/annotations/annotations.csv new file mode 100644 index 0000000000000000000000000000000000000000..7b024477248f1e0da87a7290dfe235843ba261fb --- /dev/null +++ b/backend/app/data/annotations/annotations.csv @@ -0,0 +1 @@ +image_filename,extracted_text,validated_text diff --git a/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1.png b/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1.png new file mode 100644 index 0000000000000000000000000000000000000000..18a60a8117d48ed03f3648fda49928f274d276d7 --- /dev/null +++ b/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a035d2bf9cdc5c00b59f381116d7a6cfb49d2d6866997e45e24a00d6fc8e8464 +size 178751 diff --git a/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1_perspective.png b/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1_perspective.png new file mode 100644 index 0000000000000000000000000000000000000000..1dadbeed647be87da359e1013c166911d3544b08 --- /dev/null +++ b/backend/app/data/synth_outputs/6dba8cf5-8e28-40e6-bf6c-5f1a5f62155c/enhanced_sanskrit_parchment_1_perspective.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7eb8257bdbba599e3640c5fd55952ec9dc60aa73ade391a3aea82a9e8cf95449 +size 143540 diff --git a/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1.png b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1.png new file mode 100644 index 0000000000000000000000000000000000000000..33df89689d07601c6f0cb74614c46c0c8aefb884 --- /dev/null +++ b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6a3c50581280a6e9c7af777a8ec46352d1422a6891a149a4e4e57aa5ad5e568 +size 199614 diff --git a/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_blur.png b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_blur.png new file mode 100644 index 0000000000000000000000000000000000000000..c7078539c5193ce7c33ae8e346f2f7841768aa33 --- /dev/null +++ b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_blur.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9329d31ebaa534bd7dcdeb986ab20c507b2442a73110a6c99bfb623a9348392e +size 170946 diff --git a/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_brightness.png b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_brightness.png new file mode 100644 index 0000000000000000000000000000000000000000..890e13c3de73773d5ff4c2754cff7f84c63e69bb --- /dev/null +++ b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_brightness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b758b275c50bb50c63122c4c6929c2cefed2a9011903dfab35568775cfc71d51 +size 191855 diff --git a/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_combined.png b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_combined.png new file mode 100644 index 0000000000000000000000000000000000000000..bbc3bc409c09db45bae65415c0f58e997b6605b6 --- /dev/null +++ b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_combined.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fe03fcb23bf1c7bd7f2f200d44c30a3d65cac833806c384b357bd19d7b1bd6d +size 124766 diff --git a/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_rotate.png b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd68ca098a0315c0416eff06a762733542bb845 --- /dev/null +++ b/backend/app/data/synth_outputs/81aefb9a-aaaa-4d5d-b838-48ce5b586547/enhanced_sanskrit_birch_1_rotate.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e1667b418dabd37488e45b08cc6d3794382c044f1b81cbf2a0e3128b21f52ef +size 189174 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000000000000000000000000000000000000..432ade8d24d74cfb79e9d8ce532230b38f1d0493 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,68 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +import os + +"""Main FastAPI application (serves API and static frontend). + +Note: We delay importing routers until after DATA_DIR is finalized to avoid +permission issues when creating directories on certain platforms (e.g., Spaces). +""" + + +app = FastAPI(title="Unified Backend: OCR + Synthetic") + +# CORS (dev-friendly; tighten for prod) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Storage paths (can be overridden by env, e.g., DATA_DIR=/data in Docker) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DEFAULT_DATA_DIR = os.path.join(BASE_DIR, "data") +DATA_DIR = os.getenv("DATA_DIR", DEFAULT_DATA_DIR) +try: + os.makedirs(DATA_DIR, exist_ok=True) +except Exception: + # Fallback to non-persistent tmp if permission denied + DATA_DIR = "/tmp/data" + os.environ["DATA_DIR"] = DATA_DIR + os.makedirs(DATA_DIR, exist_ok=True) + +UPLOAD_DIR = os.path.join(DATA_DIR, "uploaded_images") +SYN_OUT_DIR = os.path.join(DATA_DIR, "synth_outputs") +os.makedirs(UPLOAD_DIR, exist_ok=True) +os.makedirs(SYN_OUT_DIR, exist_ok=True) + +# Ensure child modules see the final DATA_DIR +os.environ["DATA_DIR"] = DATA_DIR + +# Routers (import after DATA_DIR is finalized so they read correct env) +from .api.routers.ocr import router as ocr_router +from .api.routers.synthetic import router as synthetic_router +app.include_router(ocr_router) +app.include_router(synthetic_router) + +# Static mounts for data +app.mount("/images", StaticFiles(directory=UPLOAD_DIR), name="images") +app.mount("/static/synthetic", StaticFiles(directory=SYN_OUT_DIR), name="synthetic") + +# Serve compiled frontend (if provided via env FRONTEND_DIST) +FRONTEND_DIST = os.getenv("FRONTEND_DIST") +if FRONTEND_DIST and os.path.isdir(FRONTEND_DIST): + app.mount("/", StaticFiles(directory=FRONTEND_DIST, html=True), name="frontend") + + +@app.get("/") +def root(): + return {"message": "Unified backend is running", "routes": ["/api/ocr", "/api/synthetic"]} + + +# Legacy compatibility (optional): +# If you want to keep old OCR paths working without frontend changes, +# you can import and map handlers or create thin wrappers here. +# For now, keep frontend updates in a later step. diff --git a/backend/app/services/__pycache__/annotations.cpython-313.pyc b/backend/app/services/__pycache__/annotations.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..231686944135070ff97504cf4176b3935e273aca Binary files /dev/null and b/backend/app/services/__pycache__/annotations.cpython-313.pyc differ diff --git a/backend/app/services/__pycache__/ocr_processor.cpython-313.pyc b/backend/app/services/__pycache__/ocr_processor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64d9dd5b70abbfd118f79785f219abd0d8f55ee1 Binary files /dev/null and b/backend/app/services/__pycache__/ocr_processor.cpython-313.pyc differ diff --git a/backend/app/services/annotations.py b/backend/app/services/annotations.py new file mode 100644 index 0000000000000000000000000000000000000000..4984db126ec2ca56a1e1934391354220be396ad7 --- /dev/null +++ b/backend/app/services/annotations.py @@ -0,0 +1,117 @@ +import os +import json +import pandas as pd +import numpy as np +from typing import Tuple, Dict, List + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, (np.float32, np.float64)): + return float(obj) + if isinstance(obj, (np.int32, np.int64)): + return int(obj) + return super().default(obj) + + +def load_annotations(path: str) -> Dict: + if not os.path.exists(path): + return {} + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def save_annotations(path: str, data: Dict): + """Save annotations to both JSON and CSV formats, merging with existing.""" + # Load existing (prefer JSON, else CSV) + json_path = path.replace('.csv', '.json') + existing: Dict[str, Dict[str, str]] = {} + + if os.path.exists(json_path): + try: + with open(json_path, "r", encoding="utf-8") as f: + existing = json.load(f) + except Exception: + existing = {} + elif os.path.exists(path): + try: + existing = read_annotations_from_csv(path) + except Exception: + existing = {} + + # Merge incoming data + merged: Dict[str, Dict[str, str]] = dict(existing) + for filename, ann in data.items(): + if filename not in merged: + merged[filename] = {"extracted_text": "", "validated_text": ""} + if isinstance(ann, dict): + if "extracted_text" in ann: + merged[filename]["extracted_text"] = str(ann.get("extracted_text", "")) + if "validated_text" in ann: + merged[filename]["validated_text"] = str(ann.get("validated_text", "")) + else: + merged[filename]["validated_text"] = str(ann) + + # Save JSON (full merged) + with open(json_path, "w", encoding="utf-8") as f: + json.dump(merged, f, ensure_ascii=False, indent=2, cls=CustomJSONEncoder) + + # Save CSV (full merged) + save_annotations_to_csv(path, merged) + + +def load_annotations_from_csv(csv_file: str, image_folder: str) -> Tuple[Dict, List[str], List[str]]: + if not os.path.exists(csv_file): + return {}, [], [] + + df = pd.read_csv(csv_file, encoding='utf-8-sig') + + if 'image_filename' not in df.columns: + raise ValueError("CSV must contain 'image_filename' column.") + + annotations: Dict[str, Dict[str, str]] = {} + valid_images: List[str] = [] + missing_images: List[str] = [] + + for _, row in df.iterrows(): + filename = row['image_filename'] + image_path = os.path.join(image_folder, filename) + if os.path.exists(image_path): + annotations[filename] = { + 'extracted_text': str(row.get('extracted_text', '')), + 'validated_text': str(row.get('validated_text', row.get('extracted_text', ''))) + } + valid_images.append(filename) + else: + missing_images.append(filename) + + return annotations, valid_images, missing_images + + +def read_annotations_from_csv(csv_file: str) -> Dict[str, Dict[str, str]]: + """Read existing CSV into a filename->annotation dict.""" + df = pd.read_csv(csv_file, encoding='utf-8-sig') + existing: Dict[str, Dict[str, str]] = {} + for _, row in df.iterrows(): + filename = str(row.get('image_filename', '')).strip() + if not filename: + continue + existing[filename] = { + 'extracted_text': str(row.get('extracted_text', '')), + 'validated_text': str(row.get('validated_text', '')) + } + return existing + + +def save_annotations_to_csv(csv_file: str, annotations: Dict[str, Dict[str, str]]): + rows = [ + { + 'image_filename': filename, + 'extracted_text': str(ann.get('extracted_text', '')), + 'validated_text': str(ann.get('validated_text', '')) + } + for filename, ann in annotations.items() + ] + df = pd.DataFrame(rows) + os.makedirs(os.path.dirname(csv_file), exist_ok=True) + df.to_csv(csv_file, index=False, encoding='utf-8-sig') diff --git a/backend/app/services/ocr_processor.py b/backend/app/services/ocr_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..450b687d12b4b0703490b5ee60865fd9eeb219b2 --- /dev/null +++ b/backend/app/services/ocr_processor.py @@ -0,0 +1,102 @@ +# Unified backend OCR processor using Google Gemini + +import os +import base64 +import logging +import time +from typing import List, Dict, Optional + +import google.generativeai as genai + +logging.basicConfig( + level=logging.INFO, + format='[%(asctime)s] %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +logger = logging.getLogger(__name__) + + +SUPPORTED_IMAGE_TYPES = {"jpg", "jpeg", "png", "bmp", "webp", "tiff"} + + +def encode_image_to_base64(image_path: str) -> Optional[str]: + if not os.path.exists(image_path): + logger.error(f"Image not found: {image_path}") + return None + try: + with open(image_path, "rb") as img_file: + return base64.b64encode(img_file.read()).decode("utf-8") + except Exception as e: + logger.error(f"Failed to read or encode image {image_path}: {e}") + return None + + +def get_mime_type(image_path: str) -> Optional[str]: + ext = image_path.split(".")[-1].lower() + if ext in SUPPORTED_IMAGE_TYPES: + return f"image/{'jpeg' if ext == 'jpg' else ext}" + logger.warning(f"Unsupported image format: {ext}") + return None + + +def run_gemini_ocr(image_path: str, api_key: str, max_retries: int = 3) -> str: + genai.configure(api_key=api_key) + model = genai.GenerativeModel("gemini-1.5-flash") + + base64_image = encode_image_to_base64(image_path) + mime_type = get_mime_type(image_path) + + if base64_image is None or mime_type is None: + return "[Image could not be processed]" + + prompt = ( + "Extract all visible Odia (ଓଡ଼ିଆ) text from the image accurately.\n" + "Only output the Odia text content. Do not explain or translate anything.\n" + "If no Odia text is found, return '[No Odia text found]'." + ) + + for attempt in range(max_retries): + try: + response = model.generate_content( + [ + prompt, + { + "mime_type": mime_type, + "data": base64_image + } + ], + generation_config={ + "temperature": 0.2, + "max_output_tokens": 2048, + "top_p": 0.8, + "top_k": 40 + } + ) + + text = response.text.strip() if response.text else "[No text extracted]" + logger.info(f"OCR complete for {os.path.basename(image_path)}") + return text + + except Exception as e: + logger.error(f"OCR attempt {attempt + 1} failed for {image_path}: {e}") + if attempt == max_retries - 1: + return f"[OCR failed after {max_retries} attempts: {str(e)}]" + time.sleep(1) + + +def batch_run_ocr(image_filenames: List[str], image_folder: str, api_key: str) -> Dict[str, str]: + results: Dict[str, str] = {} + logger.info(f"Starting batch OCR on {len(image_filenames)} images.") + + for filename in image_filenames: + image_path = os.path.join(image_folder, filename) + if not os.path.exists(image_path): + logger.error(f"Image not found: {image_path}") + results[filename] = "[Image file not found]" + continue + + results[filename] = run_gemini_ocr(image_path, api_key) + + logger.info("Batch OCR complete.") + return results + diff --git a/backend/app/services/synthetic/__init__.py b/backend/app/services/synthetic/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2e8b6f5f2d058b4da8ac933f21c2ac3d145388b9 --- /dev/null +++ b/backend/app/services/synthetic/__init__.py @@ -0,0 +1,22 @@ +""" +Synthetic Text Generator Package (moved under unified backend services) +""" + +__version__ = "1.0.0" + +from .config import ENHANCED_DEFAULT_PARAMS +from .core import ( + generate_enhanced_sanskrit_samples, + generate_comprehensive_dataset, + generate_ultra_realistic_samples, +) +from .huggingface_processor import HuggingFaceDatasetProcessor + +__all__ = [ + "ENHANCED_DEFAULT_PARAMS", + "generate_enhanced_sanskrit_samples", + "generate_comprehensive_dataset", + "generate_ultra_realistic_samples", + "HuggingFaceDatasetProcessor", +] + diff --git a/backend/app/services/synthetic/__pycache__/__init__.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d19429d057959e93fd959106704a406d896bff92 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/backgrounds.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/backgrounds.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c10e2c5e4ed7c71ba892cac3a4d156355883abe9 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/backgrounds.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/config.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c18cebe68cf749abee86a563fe3b969b699dc95 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/config.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/core.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/core.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5935117ba9d93fb6a1864163b08b0bdec181f10e Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/core.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/effects.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/effects.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26561183b1bdbc055a7598104dee6d6eb1584025 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/effects.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/huggingface_processor.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/huggingface_processor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fadd12289276f0ed709b2eb7abd707ba304b6f84 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/huggingface_processor.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/text_renderer.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/text_renderer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0a5e025ef708e6123ddf01689589c4c0c35c5a5 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/text_renderer.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/__pycache__/transformations.cpython-313.pyc b/backend/app/services/synthetic/__pycache__/transformations.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6867918459bde286721cefbb50463511441e9126 Binary files /dev/null and b/backend/app/services/synthetic/__pycache__/transformations.cpython-313.pyc differ diff --git a/backend/app/services/synthetic/backgrounds.py b/backend/app/services/synthetic/backgrounds.py new file mode 100644 index 0000000000000000000000000000000000000000..ab3571414c2143ec087155b185a5130a14085488 --- /dev/null +++ b/backend/app/services/synthetic/backgrounds.py @@ -0,0 +1,129 @@ +""" +Background generation module for creating realistic paper textures and backgrounds +""" + +import os +import random +import numpy as np +from PIL import Image +from typing import Dict +import logging +from .effects import AdvancedImageEffects + +logger = logging.getLogger(__name__) + + +def create_enhanced_background(width: int, height: int, style: str, params: Dict) -> Image.Image: + if params.get("image_dir") and os.path.exists(params["image_dir"]): + image_files = [f for f in os.listdir(params["image_dir"]) if f.lower().endswith((".png", ".jpg", ".jpeg"))] + if image_files: + img_path = os.path.join(params["image_dir"], random.choice(image_files)) + try: + bg_img = Image.open(img_path).convert('RGB') + bg_img = bg_img.resize((width, height), Image.LANCZOS) + return bg_img + except Exception as e: + logger.error(f"Error loading background image {img_path}: {e}") + + if params.get('fiber_density', 0) > 0: + fiber_texture = AdvancedImageEffects.simulate_paper_fiber_texture(width, height, params['fiber_density']) + else: + fiber_texture = np.zeros((height, width, 3), dtype=np.uint8) + + if style == "lined_paper": + background = np.ones((height, width, 3), dtype=np.uint8) * [210, 180, 140] + background = np.clip(background.astype(np.float32) - fiber_texture, 0, 255).astype(np.uint8) + line_spacing = random.randint(15, 25) + for y in range(0, height, line_spacing): + line_width = random.randint(1, 2) + darkness = random.randint(6, 20) * params["texture"] + if y + line_width < height: + background[y:y+line_width, :, :] = np.clip(background[y:y+line_width, :, :] - darkness, 0, 255) + noise = np.random.randint(0, int(15 * params["noise"]), (height, width, 3), dtype=np.uint8) + background = np.clip(background - noise, 0, 255).astype(np.uint8) + stain_count = int(random.randint(2, 4) * params["stains"]) + for _ in range(stain_count): + x = random.randint(0, width-100) + y = random.randint(0, height-100) + size = random.randint(20, 60) + darkness = random.randint(8, 25) * params["stain_intensity"] + stain_mask = np.zeros((size, size), dtype=np.float32) + center = size // 2 + for i in range(size): + for j in range(size): + dist = np.sqrt((i - center)**2 + (j - center)**2) + if dist < center: + stain_mask[i, j] = (1 - dist / center) * np.random.uniform(0.4, 1.0) + end_y = min(y + size, height) + end_x = min(x + size, width) + actual_size_y = end_y - y + actual_size_x = end_x - x + if actual_size_y > 0 and actual_size_x > 0: + stain_region = stain_mask[:actual_size_y, :actual_size_x] + for c in range(3): + background[y:end_y, x:end_x, c] = np.clip( + background[y:end_y, x:end_x, c] - darkness * stain_region * params["stain_intensity"], 0, 255 + ) + + elif style == "old_paper": + background = np.ones((height, width, 3), dtype=np.uint8) * [236, 222, 181] + background = np.clip(background.astype(np.float32) - fiber_texture, 0, 255).astype(np.uint8) + noise = np.random.randint(0, int(12 * params["noise"]), (height, width, 3), dtype=np.uint8) + background = np.clip(background - noise, 0, 255).astype(np.uint8) + edge_width = width // 10 + for i in range(edge_width): + factor = (edge_width - i) / edge_width * 15 * params["aging"] + aging_noise = np.random.uniform(0.5, 1.5, (height, width)) + if i < height: + background[i, :, 2] = np.clip(background[i, :, 2] - factor * aging_noise[i, :], 0, 255) + if height - i - 1 >= 0: + background[height-i-1, :, 2] = np.clip(background[height-i-1, :, 2] - factor * aging_noise[height-i-1, :], 0, 255) + if i < width: + background[:, i, 2] = np.clip(background[:, i, 2] - factor * aging_noise[:, i], 0, 255) + if width - i - 1 >= 0: + background[:, width-i-1, 2] = np.clip(background[:, width-i-1, 2] - factor * aging_noise[:, width-i-1], 0, 255) + + elif style == "birch": + background = np.ones((height, width, 3), dtype=np.uint8) * [235, 225, 215] + background = np.clip(background.astype(np.float32) - fiber_texture, 0, 255).astype(np.uint8) + noise = np.random.randint(0, int(10 * params["noise"]), (height, width, 3), dtype=np.uint8) + background = np.clip(background - noise, 0, 255).astype(np.uint8) + variation_count = int(150 * params["texture"]) + for _ in range(variation_count): + x = random.randint(0, width-1) + y = random.randint(0, height-1) + size = random.randint(10, 25) + variation = random.randint(-6, 6) * params["texture"] + for i in range(-size, size): + for j in range(-size, size): + dist = np.sqrt(i*i + j*j) + if dist <= size: + shape_factor = np.random.uniform(0.7, 1.3) + if dist <= size * shape_factor: + yi, xi = y + i, x + j + if 0 <= yi < height and 0 <= xi < width: + background[yi, xi, :] = np.clip(background[yi, xi, :] + variation, 0, 255) + + else: # parchment + background = np.ones((height, width, 3), dtype=np.uint8) * [230, 215, 185] + background = np.clip(background.astype(np.float32) - fiber_texture, 0, 255).astype(np.uint8) + variation_count = int(400 * params["texture"]) + for _ in range(variation_count): + x = random.randint(0, width-1) + y = random.randint(0, height-1) + size = random.randint(5, 12) + variation = random.randint(-7, 7) * params["texture"] + for i in range(-size, size): + for j in range(-size, size): + dist = np.sqrt(i*i + j*j) + if dist <= size: + grain_factor = 1 + 0.3 * np.sin(j * 0.5) * np.cos(i * 0.3) + if dist <= size * grain_factor: + yi, xi = y + i, x + j + if 0 <= yi < height and 0 <= xi < width: + background[yi, xi, :] = np.clip(background[yi, xi, :] + variation, 0, 255) + noise = np.random.randint(0, int(8 * params["noise"]), (height, width, 3), dtype=np.uint8) + background = np.clip(background - noise, 0, 255).astype(np.uint8) + + return Image.fromarray(background) + diff --git a/backend/app/services/synthetic/config.py b/backend/app/services/synthetic/config.py new file mode 100644 index 0000000000000000000000000000000000000000..61198151a45491533175e0656b064ce6b2345383 --- /dev/null +++ b/backend/app/services/synthetic/config.py @@ -0,0 +1,61 @@ +""" +Configuration parameters for the Synthetic Text Generator +""" + +ENHANCED_DEFAULT_PARAMS = { + 'width': 400, + 'height': 320, + 'base_images': 1, + + 'font_dir': './content/static', + 'font': 'NotoSansOriya_Condensed-Regular.ttf', + + 'noise': 0.7, + 'aging': 0.6, + 'texture': 0.7, + 'stains': 0.6, + 'stain_intensity': 0.5, + + 'word_position': 0.6, + 'ink_color': 0.5, + 'line_spacing': 0.4, + 'baseline': 0.3, + 'word_angle': 0.0, + + 'apply_transforms': True, + 'all_transforms': False, + 'rotation_max': 5.0, + 'brightness_var': 0.2, + 'contrast_var': 0.2, + 'noise_min': 0.01, + 'noise_max': 0.05, + 'blur_min': 0.5, + 'blur_max': 1.0, + + 'fold_intensity': 0.3, + 'bleed_intensity': 0.3, + 'bleed_radius': 3, + 'corner_displacement': 20, + 'morph_operation': 'mixed', + 'morph_kernel_size': 3, + 'aging_intensity': 0.5, + 'fiber_density': 0.5, + 'enable_advanced_effects': True, + 'advanced_effect_probability': 0.7, + 'shadow_angle': 45, + 'shadow_intensity': 0.4, + 'lens_distortion_strength': 0.2, + 'scanner_artifacts': True, + 'compression_quality': 85, + 'fold_probability': 0.4, + 'crease_probability': 0.3, + 'perspective_probability': 0.5, + 'shadow_probability': 0.6, + + 'use_multiprocessing': False, + 'num_processes': 4, + 'enable_caching': True, + 'debug_mode': False, + 'image_dir': '' +} + diff --git a/backend/app/services/synthetic/core.py b/backend/app/services/synthetic/core.py new file mode 100644 index 0000000000000000000000000000000000000000..f314aba7a90bd61776e2cc766125ef54a73a9ebb --- /dev/null +++ b/backend/app/services/synthetic/core.py @@ -0,0 +1,230 @@ +""" +Core module containing main generation functions +""" + +import os +import random +import logging +from typing import Dict, List, Optional +from PIL import Image +from .config import ENHANCED_DEFAULT_PARAMS +from .text_renderer import render_enhanced_sanskrit +from .transformations import ( + apply_enhanced_postprocessing, + create_comprehensive_effect_combinations, + apply_systematic_postprocessing, +) + +logger = logging.getLogger(__name__) + + +def generate_enhanced_sanskrit_samples( + text: str, + font_path: str = None, + output_dir: str = None, + params: Dict = None, +) -> Optional[List[Image.Image]]: + if params is None: + params = ENHANCED_DEFAULT_PARAMS.copy() + else: + params = {**ENHANCED_DEFAULT_PARAMS, **params} + + if font_path is None: + font_path = os.path.join(params['font_dir'], params['font']) + + if not os.path.exists(font_path): + logger.error(f"Font not found at {font_path}") + return [] if output_dir is None else None + + styles = ["lined_paper", "old_paper", "birch", "parchment"] + + ink_colors = { + "lined_paper": (60, 30, 10), + "old_paper": (20, 20, 20), + "birch": (50, 20, 10), + "parchment": (10, 10, 10), + } + + width, height = params['width'], params['height'] + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + sampled_styles = random.choices(styles, k=params['base_images']) + style_counts = {style: sampled_styles.count(style) for style in styles} + logger.info(f"Randomly selected styles: {style_counts}") + + base_images = [] + + for style, count in style_counts.items(): + for i in range(count): + font_size = random.randint(12, 18) + output_path = ( + os.path.join(output_dir, f"enhanced_sanskrit_{style}_{i+1}.png") + if output_dir + else None + ) + + img = render_enhanced_sanskrit( + text=text, + font_path=font_path, + output_path=output_path, + width=width, + height=height, + font_size=font_size, + style=style, + ink_color=ink_colors[style], + params=params, + ) + + if img: + base_images.append(img) + if params['apply_transforms'] and output_dir: + base_filename = f"enhanced_sanskrit_{style}_{i+1}" + transformed_images = apply_enhanced_postprocessing( + img, output_dir, base_filename, params + ) + base_images.extend(transformed_images[1:]) + + return base_images if output_dir is None else None + + +def generate_comprehensive_dataset( + text: str, font_path: str = None, output_dir: str = None, params: Dict = None +) -> List[Image.Image]: + if params is None: + params = ENHANCED_DEFAULT_PARAMS.copy() + else: + params = {**ENHANCED_DEFAULT_PARAMS, **params} + + if font_path is None: + font_path = os.path.join(params['font_dir'], params['font']) + + if not os.path.exists(font_path): + logger.error(f"Font not found at {font_path}") + return [] + + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + effect_combinations = create_comprehensive_effect_combinations() + + styles = ["lined_paper", "old_paper", "birch", "parchment"] + ink_colors = { + "lined_paper": (60, 30, 10), + "old_paper": (20, 20, 20), + "birch": (50, 20, 10), + "parchment": (10, 10, 10), + } + + width, height = params['width'], params['height'] + all_generated_images: List[Image.Image] = [] + + logger.info( + f"Generating comprehensive dataset with {len(effect_combinations)} effect combinations" + ) + + for style in styles: + font_size = random.randint(14, 18) + output_path = os.path.join(output_dir, f"base_{style}.png") if output_dir else None + + base_image = render_enhanced_sanskrit( + text=text, + font_path=font_path, + output_path=output_path, + width=width, + height=height, + font_size=font_size, + style=style, + ink_color=ink_colors[style], + params=params, + ) + + if base_image: + all_generated_images.append(base_image) + for combo_idx, effect_combo in enumerate(effect_combinations): + base_filename = f"comprehensive_{style}_{combo_idx:03d}" + enhanced_images = apply_systematic_postprocessing( + base_image, output_dir, base_filename, params, effect_combo + ) + all_generated_images.extend(enhanced_images[1:]) + + logger.info(f"Total images generated: {len(all_generated_images)}") + return all_generated_images + + +def generate_ultra_realistic_samples( + text: str, output_dir: str = None, style_focus: str = None, params: Dict = None +) -> List[Image.Image]: + if params is None: + params = ENHANCED_DEFAULT_PARAMS.copy() + + ultra_realistic_params = { + **params, + 'fold_intensity': 0.4, + 'bleed_intensity': 0.35, + 'shadow_intensity': 0.5, + 'lens_distortion_strength': 0.15, + 'aging_intensity': 0.7, + 'fiber_density': 0.6, + 'texture': 0.8, + 'noise': 0.6, + 'stains': 0.7, + 'stain_intensity': 0.6, + } + + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + ultra_combinations = [ + ["fold_crease", "ink_bleed", "shadow_cast", "scanner_artifacts"], + ["perspective", "morphological", "lens_distortion", "washboard"], + ["cylinder", "scanner_artifacts", "lens_distortion", "shadow_cast"], + ["fold_crease", "ink_bleed", "morphological", "perspective"], + [ + "fold_crease", + "ink_bleed", + "perspective", + "shadow_cast", + "morphological", + "scanner_artifacts", + "lens_distortion", + ], + ["perspective", "lens_distortion", "shadow_cast", "cylinder"], + ["washboard", "ink_bleed", "morphological", "fold_crease"], + ] + + font_path = os.path.join(ultra_realistic_params['font_dir'], ultra_realistic_params['font']) + + styles = ["lined_paper", "old_paper", "birch", "parchment"] if not style_focus else [style_focus] + ink_colors = { + "lined_paper": (60, 30, 10), + "old_paper": (20, 20, 20), + "birch": (50, 20, 10), + "parchment": (10, 10, 10), + } + + all_images: List[Image.Image] = [] + + for style in styles: + base_image = render_enhanced_sanskrit( + text=text, + font_path=font_path, + output_path=None, + width=ultra_realistic_params['width'], + height=ultra_realistic_params['height'], + font_size=random.randint(14, 18), + style=style, + ink_color=ink_colors[style], + params=ultra_realistic_params, + ) + + if base_image: + for combo_idx, effect_combo in enumerate(ultra_combinations): + base_filename = f"ultra_realistic_{style}_{combo_idx:02d}" + enhanced_images = apply_systematic_postprocessing( + base_image, output_dir, base_filename, ultra_realistic_params, effect_combo + ) + all_images.extend(enhanced_images[1:]) + + return all_images + diff --git a/backend/app/services/synthetic/effects.py b/backend/app/services/synthetic/effects.py new file mode 100644 index 0000000000000000000000000000000000000000..1b19a0288d22b6bf947a37161dc1db1b0a27ec04 --- /dev/null +++ b/backend/app/services/synthetic/effects.py @@ -0,0 +1,218 @@ +""" +Advanced image effects for synthetic text generation +""" + +import cv2 +import numpy as np +import random +import logging +from typing import List, Tuple +from noise import pnoise2 + +logger = logging.getLogger(__name__) + + +class EffectPlugin: + def __init__(self, name: str, params: dict): + self.name = name + self.params = params + self.validate_params() + + def apply(self, image: np.ndarray) -> np.ndarray: + raise NotImplementedError + + def validate_params(self): + pass + + +class AdvancedImageEffects: + @staticmethod + def generate_perlin_noise(width: int, height: int, scale: float = 0.1, octaves: int = 4) -> np.ndarray: + noise_map = np.zeros((height, width)) + for i in range(height): + for j in range(width): + noise_map[i][j] = pnoise2(i * scale, j * scale, octaves=octaves) + return noise_map + + @staticmethod + def simulate_paper_fiber_texture(width: int, height: int, fiber_density: float = 0.5) -> np.ndarray: + try: + base_texture = AdvancedImageEffects.generate_perlin_noise(width, height, 0.02, 4) + fine_texture = AdvancedImageEffects.generate_perlin_noise(width, height, 0.1, 2) + combined = base_texture * 0.7 + fine_texture * 0.3 + combined = ((combined + 1) / 2) * fiber_density * 20 + texture = np.stack([combined, combined, combined], axis=2) + return texture.astype(np.uint8) + except Exception as e: + logger.warning(f"Failed to generate Perlin noise texture: {e}") + return np.random.randint(0, int(20 * fiber_density), (height, width, 3), dtype=np.uint8) + + @staticmethod + def simulate_fold_crease(image: np.ndarray, fold_lines: List[Tuple], fold_intensity: float = 0.5) -> np.ndarray: + try: + height, width = image.shape[:2] + result = image.copy() + for fold_line in fold_lines: + y_coords, x_coords = np.ogrid[:height, :width] + x1, y1, x2, y2 = fold_line + line_length = np.sqrt((x2 - x1)**2 + (y2 - y1)**2) + if line_length > 0: + distances = np.abs((y2 - y1) * x_coords - (x2 - x1) * y_coords + x2 * y1 - y2 * x1) / line_length + fold_width = min(width, height) * 0.1 + fold_profile = np.exp(-0.5 * (distances / fold_width)**2) + fold_effect = fold_profile * fold_intensity * 40 + shadow_mask = (y_coords - y1) * (x2 - x1) - (x_coords - x1) * (y2 - y1) > 0 + shadow_effect = fold_profile * shadow_mask * fold_intensity * 20 + result = result.astype(np.float32) + result[:, :, 0] -= fold_effect + shadow_effect + result[:, :, 1] -= fold_effect + shadow_effect + result[:, :, 2] -= fold_effect + shadow_effect + result = np.clip(result, 0, 255).astype(np.uint8) + return result + except Exception as e: + logger.error(f"Error in fold/crease simulation: {e}") + return image + + @staticmethod + def simulate_ink_bleed(image: np.ndarray, bleed_intensity: float = 0.3, bleed_radius: int = 3) -> np.ndarray: + try: + gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) + _, text_mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV) + kernel_size = max(1, int(bleed_radius * 2 + 1)) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)) + bleeding_mask = cv2.dilate(text_mask, kernel, iterations=1) + bleed_effect = cv2.GaussianBlur(bleeding_mask.astype(np.float32), (kernel_size, kernel_size), 0) + bleed_effect = bleed_effect * bleed_intensity / 255.0 + result = image.astype(np.float32) + for c in range(3): + result[:, :, c] = result[:, :, c] * (1 - 0.5 * bleed_effect) + return np.clip(result, 0, 255).astype(np.uint8) + except Exception as e: + logger.error(f"Error in ink bleed simulation: {e}") + return image + + @staticmethod + def apply_perspective_distortion(image: np.ndarray, corner_displacement: int = 20) -> np.ndarray: + try: + height, width = image.shape[:2] + src_points = np.float32([[0, 0], [width, 0], [width, height], [0, height]]) + dst_points = src_points.copy() + for i in range(4): + dst_points[i][0] += random.randint(-corner_displacement, corner_displacement) + dst_points[i][1] += random.randint(-corner_displacement, corner_displacement) + dst_points[:, 0] = np.clip(dst_points[:, 0], -width*0.1, width*1.1) + dst_points[:, 1] = np.clip(dst_points[:, 1], -height*0.1, height*1.1) + matrix = cv2.getPerspectiveTransform(src_points, dst_points) + result = cv2.warpPerspective( + image, matrix, (width, height), borderMode=cv2.BORDER_REPLICATE + ) + return result + except Exception as e: + logger.error(f"Error in perspective distortion: {e}") + return image + + @staticmethod + def apply_shadow_effects(image: np.ndarray, shadow_angle: float = 45, shadow_intensity: float = 0.4) -> np.ndarray: + try: + height, width = image.shape[:2] + result = image.copy().astype(np.float32) + angle_rad = np.radians(shadow_angle) + x_coords, y_coords = np.meshgrid(np.arange(width), np.arange(height)) + shadow_factor = (np.cos(angle_rad) * x_coords / width + np.sin(angle_rad) * y_coords / height) + shadow_factor = np.clip(shadow_factor, 0, 1) + shadow_effect = 1 - shadow_factor * shadow_intensity + for c in range(3): + result[:, :, c] *= shadow_effect + return np.clip(result, 0, 255).astype(np.uint8) + except Exception as e: + logger.error(f"Error in shadow effects: {e}") + return image + + @staticmethod + def apply_morphological_operations(image: np.ndarray, operation: str = 'mixed', kernel_size: int = 3) -> np.ndarray: + try: + gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)) + if operation == 'erosion': + processed = cv2.erode(gray, kernel, iterations=1) + elif operation == 'dilation': + processed = cv2.dilate(gray, kernel, iterations=1) + elif operation == 'opening': + processed = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel) + elif operation == 'closing': + processed = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel) + else: + ops = ['erosion', 'dilation', 'opening', 'closing'] + import random as _r + chosen = _r.choice(ops) + return AdvancedImageEffects.apply_morphological_operations(image, chosen, kernel_size) + return cv2.cvtColor(processed, cv2.COLOR_GRAY2RGB) + except Exception as e: + logger.error(f"Error in morphological operations: {e}") + return image + + @staticmethod + def simulate_scanner_artifacts(image: np.ndarray, compression_quality: int = 85) -> np.ndarray: + try: + height, width = image.shape[:2] + result = image.copy() + for y in range(0, height, random.randint(8, 15)): + intensity = random.randint(5, 15) + if y < height: + result[y, :, :] = np.clip(result[y, :, :] - intensity, 0, 255) + dust_count = random.randint(3, 8) + for _ in range(dust_count): + x = random.randint(0, width - 5) + y = random.randint(0, height - 5) + size = random.randint(2, 5) + dust_intensity = random.randint(20, 40) + result[y:y+size, x:x+size, :] = np.clip(result[y:y+size, x:x+size, :] - dust_intensity, 0, 255) + encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), compression_quality] + _, encimg = cv2.imencode('.jpg', cv2.cvtColor(result, cv2.COLOR_RGB2BGR), encode_param) + result = cv2.imdecode(encimg, cv2.IMREAD_COLOR) + result = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) + return result + except Exception as e: + logger.error(f"Error in scanner artifacts: {e}") + return image + + @staticmethod + def apply_lens_distortion(image: np.ndarray, strength: float = 0.2) -> np.ndarray: + try: + height, width = image.shape[:2] + center_x, center_y = width // 2, height // 2 + y_coords, x_coords = np.ogrid[:height, :width] + distances = np.sqrt((x_coords - center_x)**2 + (y_coords - center_y)**2) + max_distance = np.sqrt(center_x**2 + center_y**2) + normalized_distances = distances / max_distance + distortion_factor = 1 + strength * normalized_distances**2 + map_x = ((x_coords - center_x) / distortion_factor + center_x).astype(np.float32) + map_y = ((y_coords - center_y) / distortion_factor + center_y).astype(np.float32) + result = cv2.remap(image, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + return result + except Exception as e: + logger.error(f"Error in lens distortion: {e}") + return image + + +def generate_random_fold_lines(image_size: Tuple[int, int], num_folds: int = None) -> List[Tuple]: + width, height = image_size + if num_folds is None: + num_folds = random.randint(1, 3) + fold_lines = [] + for _ in range(num_folds): + x1 = random.randint(0, width) + y1 = random.randint(0, height) + x2 = random.randint(0, width) + y2 = random.randint(0, height) + fold_lines.append((x1, y1, x2, y2)) + return fold_lines + + +def safe_apply_effect(effect_func, image: np.ndarray, effect_name: str) -> np.ndarray: + try: + return effect_func(image) + except Exception as e: + logger.error(f"Error applying {effect_name}: {e}") + return image + diff --git a/backend/app/services/synthetic/huggingface_processor.py b/backend/app/services/synthetic/huggingface_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..c1bb152cc7199584c2820e7cfdd06705e45cfad1 --- /dev/null +++ b/backend/app/services/synthetic/huggingface_processor.py @@ -0,0 +1,228 @@ +""" +Hugging Face dataset processor for downloading datasets and generating synthetic text images +""" + +import os +import logging +import pandas as pd +from typing import Dict, List, Optional +from urllib.parse import urlparse +import requests +from PIL import Image + +from datasets import load_dataset +import datasets + +from .config import ENHANCED_DEFAULT_PARAMS +from .text_renderer import render_enhanced_sanskrit +from .transformations import apply_enhanced_postprocessing + +logger = logging.getLogger(__name__) + + +class HuggingFaceDatasetProcessor: + def __init__(self, output_dir: str = "hf_dataset_output", params: Dict = None): + self.output_dir = output_dir + self.params = params if params else ENHANCED_DEFAULT_PARAMS.copy() + self.image_dir = os.path.join(output_dir, "images") + self.csv_path = os.path.join(output_dir, "dataset.csv") + os.makedirs(self.image_dir, exist_ok=True) + self.ink_colors = { + "lined_paper": (60, 30, 10), + "old_paper": (20, 20, 20), + "birch": (50, 20, 10), + "parchment": (10, 10, 10), + } + + def load_huggingface_dataset(self, dataset_name: str, config_name: str = None, split: str = None, streaming: bool = False): + try: + dataset = load_dataset(dataset_name, config_name, split=split, streaming=streaming) + if streaming: + return dataset + else: + if isinstance(dataset, datasets.DatasetDict): + if split: + df = dataset[split].to_pandas() + elif 'train' in dataset: + df = dataset['train'].to_pandas() + else: + first_split = list(dataset.keys())[0] + df = dataset[first_split].to_pandas() + else: + df = dataset.to_pandas() + return df + except Exception as e: + logger.error(f"Error loading Hugging Face dataset: {e}") + return None + + def download_dataset_from_url(self, url: str, output_file: str = "dataset.csv") -> bool: + try: + if "huggingface.co/datasets" in url: + parsed = urlparse(url) + dataset_path = parsed.path.strip('/') + if "/blob/main/" in url: + raw_url = url.replace("/blob/main/", "/raw/main/") + elif "/tree/main" in url: + raw_url = url.replace("/tree/main", "/raw/main/dataset.csv") + else: + raw_url = f"https://huggingface.co/{dataset_path}/raw/main/dataset.csv" + else: + raw_url = url + response = requests.get(raw_url, stream=True) + response.raise_for_status() + file_path = os.path.join(self.output_dir, output_file) + with open(file_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + return True + except Exception as e: + logger.error(f"Error downloading dataset: {e}") + return False + + def load_dataset(self, file_path: str, text_column: str) -> Optional[pd.DataFrame]: + try: + encodings = ['utf-8', 'iso-8859-1', 'windows-1252', 'utf-16'] + df = None + for encoding in encodings: + try: + df = pd.read_csv(file_path, encoding=encoding) + break + except UnicodeDecodeError: + continue + if df is None: + raise Exception("Could not load dataset with any supported encoding") + if text_column not in df.columns: + raise Exception(f"Column '{text_column}' not found. Available columns: {list(df.columns)}") + initial_rows = len(df) + df = df.dropna(subset=[text_column]) + df = df[df[text_column].str.strip() != ""] + return df + except Exception as e: + logger.error(f"Error loading dataset: {e}") + return None + + def generate_images_from_dataset(self, dataset_df: pd.DataFrame, text_column: str, max_samples: int = None) -> List[Dict]: + results = [] + if max_samples and max_samples < len(dataset_df): + dataset_df = dataset_df.head(max_samples) + styles = ["lined_paper", "old_paper", "birch", "parchment"] + for idx, row in dataset_df.iterrows(): + try: + text = str(row[text_column]).strip() + if not text: + continue + style = styles[idx % len(styles)] + base_filename = f"text_image_{idx:06d}" + image_filename = f"{base_filename}.png" + image_path = os.path.join(self.image_dir, image_filename) + img = render_enhanced_sanskrit( + text=text, + font_path=os.path.join(self.params['font_dir'], self.params['font']), + output_path=None, + width=self.params['width'], + height=self.params['height'], + font_size=14, + style=style, + ink_color=self.ink_colors[style], + params=self.params, + ) + if img is None: + continue + if self.params.get('apply_transforms', True): + transformed_images = apply_enhanced_postprocessing(img, None, base_filename, self.params) + final_img = transformed_images[-1] if len(transformed_images) > 1 else img + else: + final_img = img + final_img.save(image_path) + result = { + 'row_index': idx, + 'image_path': os.path.relpath(image_path, self.output_dir), + 'text': text, + 'style': style, + 'image_filename': image_filename, + } + for col in dataset_df.columns: + if col != text_column: + result[col] = row[col] + results.append(result) + except Exception as e: + logger.error(f"Error processing row {idx}: {e}") + continue + return results + + def save_results_csv(self, results: List[Dict], additional_info: Dict = None): + try: + if not results: + return + df = pd.DataFrame(results) + important_cols = ['image_path', 'text', 'style', 'image_filename'] + other_cols = [col for col in df.columns if col not in important_cols] + df = df[important_cols + other_cols] + df.to_csv(self.csv_path, index=False, encoding='utf-8') + if additional_info: + metadata_path = os.path.join(self.output_dir, "metadata.txt") + with open(metadata_path, 'w', encoding='utf-8') as f: + f.write("Dataset Processing Metadata\n") + f.write("=" * 30 + "\n") + for key, value in additional_info.items(): + f.write(f"{key}: {value}\n") + except Exception as e: + logger.error(f"Error saving results: {e}") + + def process_huggingface_dataset(self, dataset_identifier: str, text_column: str, max_samples: int = None, config_name: str = None, split: str = None) -> bool: + try: + df = None + if not dataset_identifier.startswith("http"): + df = self.load_huggingface_dataset(dataset_identifier, config_name=config_name, split=split) + if df is None and dataset_identifier.startswith("http"): + dataset_file = "downloaded_dataset.csv" + if self.download_dataset_from_url(dataset_identifier, dataset_file): + dataset_path = os.path.join(self.output_dir, dataset_file) + df = self.load_dataset(dataset_path, text_column) + if df is None: + return False + if text_column not in df.columns: + return False + results = self.generate_images_from_dataset(df, text_column, max_samples) + if not results: + return False + additional_info = { + "dataset_identifier": dataset_identifier, + "config_name": config_name, + "split": split, + "text_column": text_column, + "original_rows": len(df), + "processed_rows": len(results), + "max_samples": max_samples or "all", + "output_directory": self.output_dir, + "image_directory": self.image_dir, + } + self.save_results_csv(results, additional_info) + return True + except Exception as e: + logger.error(f"Error in dataset processing workflow: {e}") + return False + + def process_local_csv(self, csv_path: str, text_column: str, max_samples: int = None) -> bool: + try: + df = self.load_dataset(csv_path, text_column) + if df is None: + return False + results = self.generate_images_from_dataset(df, text_column, max_samples) + if not results: + return False + additional_info = { + "source_file": csv_path, + "text_column": text_column, + "original_rows": len(df), + "processed_rows": len(results), + "max_samples": max_samples or "all", + "output_directory": self.output_dir, + "image_directory": self.image_dir, + } + self.save_results_csv(results, additional_info) + return True + except Exception as e: + logger.error(f"Error processing local CSV: {e}") + return False + diff --git a/backend/app/services/synthetic/text_renderer.py b/backend/app/services/synthetic/text_renderer.py new file mode 100644 index 0000000000000000000000000000000000000000..067fd9cee752d665f1a26da7c4e19642d3912891 --- /dev/null +++ b/backend/app/services/synthetic/text_renderer.py @@ -0,0 +1,112 @@ +""" +Text rendering module for Sanskrit/Oriya text with various effects +""" + +import os +import math +import random +import logging +from typing import Dict, Tuple, Optional +import numpy as np +from PIL import Image, ImageDraw, ImageFont +from .backgrounds import create_enhanced_background + +logger = logging.getLogger(__name__) + + +def render_enhanced_sanskrit( + text: str, + font_path: str, + output_path: str, + width: int, + height: int, + font_size: int, + style: str, + ink_color: Tuple[int, int, int], + params: Dict, +) -> Optional[Image.Image]: + img = create_enhanced_background(width, height, style, params) + draw = ImageDraw.Draw(img) + + try: + font = ImageFont.truetype(font_path, font_size) + words = text.strip().replace('\n', ' ').split() + y_position = random.randint(25, 75) + margin = 25 + available_width = width - 2 * margin + space_width = draw.textlength(" ", font=font) + + current_line = [] + current_line_width = 0 + all_lines = [] + for word in words: + word_width = draw.textlength(word, font=font) + if current_line and current_line_width + space_width + word_width > available_width: + all_lines.append(current_line) + current_line = [word] + current_line_width = word_width + else: + if current_line: + current_line_width += space_width + word_width + else: + current_line_width = word_width + current_line.append(word) + if current_line: + all_lines.append(current_line) + + for line in all_lines: + line_text = " ".join(line) + line_width = draw.textlength(line_text, font=font) + x_position = (width - line_width) // 2 + baseline_offset = random.randint(-2, 2) * params["baseline"] + y_line_position = y_position + baseline_offset + if y_line_position + font_size > height - margin: + break + x_word_position = x_position + for word in line: + word_x_offset = int(random.uniform(-1.5, 1.5) * params["word_position"]) + word_y_offset = int(random.uniform(-1, 1) * params["word_position"]) + color_variation = int(random.randint(-3, 3) * params["ink_color"]) + word_color = ( + np.clip(ink_color[0] + color_variation, 0, 255), + np.clip(ink_color[1] + color_variation, 0, 255), + np.clip(ink_color[2] + color_variation, 0, 255), + ) + word_width = draw.textlength(word, font=font) + word_height = font_size * 1.2 + if params["word_angle"] > 0: + word_angle = random.uniform(-2, 2) * params["word_angle"] + diagonal = math.sqrt(word_width**2 + word_height**2) + padding = int(diagonal * 0.5) + temp_width = int(diagonal + 2 * padding) + temp_height = int(diagonal + 2 * padding) + txt_img = Image.new('RGBA', (temp_width, temp_height), (0, 0, 0, 0)) + txt_d = ImageDraw.Draw(txt_img) + center_x = temp_width // 2 - word_width // 2 + center_y = temp_height // 2 - word_height // 2 + txt_d.text((center_x, center_y), word, font=font, fill=word_color + (255,)) + rotated = txt_img.rotate( + word_angle, resample=Image.BICUBIC, expand=0, center=(temp_width//2, temp_height//2) + ) + paste_x = int(x_word_position + word_x_offset - padding) + paste_y = int(y_line_position + word_y_offset - padding) + img.paste(rotated, (paste_x, paste_y), rotated) + else: + draw.text( + (x_word_position + word_x_offset, y_line_position + word_y_offset), + word, fill=word_color, font=font + ) + x_word_position += word_width + space_width + line_spacing_factor = 1.0 + (random.uniform(-0.1, 0.1) * params["line_spacing"]) + y_position += int(font_size * 1.2 * line_spacing_factor) + + if output_path is not None: + img.save(output_path) + logger.info(f"Saved rendered Sanskrit to {output_path}") + + return img + + except Exception as e: + logger.error(f"Error rendering text with font {font_path}: {e}") + return None + diff --git a/backend/app/services/synthetic/transformations.py b/backend/app/services/synthetic/transformations.py new file mode 100644 index 0000000000000000000000000000000000000000..ec63a7c39b7d879afc796ca6bee00c5a0d3a6a97 --- /dev/null +++ b/backend/app/services/synthetic/transformations.py @@ -0,0 +1,249 @@ +""" +Transformations module for geometric transformations and post-processing effects +""" + +import cv2 +import os +import random +import logging +import itertools +from math import pi +from typing import List, Dict +import numpy as np +from PIL import Image, ImageEnhance, ImageFilter +from .effects import AdvancedImageEffects, generate_random_fold_lines, safe_apply_effect + +logger = logging.getLogger(__name__) + + +def cylindrical_edge_warp(pil_img: Image.Image, side: str = "left", strength: float = 0.6, warp_portion: float = 0.45) -> Image.Image: + try: + img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) + h, w = img.shape[:2] + W = int(warp_portion * w) + R = W / strength if strength != 0 else 1e9 + X, Y = np.meshgrid(np.arange(w), np.arange(h)) + map_x = X.astype(np.float32).copy() + map_y = Y.astype(np.float32).copy() + if side == "left": + strip = X < W + dx = W - X[strip] + else: + strip = X > (w - W) + dx = X[strip] - (w - W) + theta = dx / R + displacement = R * np.sin(theta) - dx + map_x[strip] += displacement + scale_y = np.cos(theta) + map_y[strip] = (Y[strip] - h/2) / scale_y + h/2 + warped = cv2.remap(img, map_x, map_y, interpolation=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) + return Image.fromarray(cv2.cvtColor(warped, cv2.COLOR_BGR2RGB)) + except Exception as e: + logger.error(f"Error in cylindrical warp: {e}") + return pil_img + + +def washboard_warp(pil_img: Image.Image, amplitude: float = 8, wavelength: float = 120, phase: float = 0.0, decay_from_top: bool = True) -> Image.Image: + try: + img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) + h, w = img.shape[:2] + x = np.arange(w, dtype=np.float32) + dy = amplitude * np.sin(2*pi*x / wavelength + phase) + if decay_from_top: + atten = np.linspace(1, 0.2, h, dtype=np.float32)[:, None] + else: + atten = 1.0 + map_x, map_y = np.meshgrid(x, np.arange(h, dtype=np.float32)) + map_y += dy * atten + warped = cv2.remap(img, map_x, map_y, cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) + return Image.fromarray(cv2.cvtColor(warped, cv2.COLOR_BGR2RGB)) + except Exception as e: + logger.error(f"Error in washboard warp: {e}") + return pil_img + + +def apply_enhanced_postprocessing(original_image: Image.Image, output_dir: str, base_filename: str, params: Dict) -> List[Image.Image]: + all_images = [original_image] + transforms = [] + + def rotate_image(img, angle): + bg_color = tuple(np.array(img).mean(axis=(0, 1)).astype(int)) + return img.rotate(angle, resample=Image.BICUBIC, expand=False, fillcolor=bg_color) + + def adjust_brightness(img, factor): + enhancer = ImageEnhance.Brightness(img) + return enhancer.enhance(factor) + + def adjust_contrast(img, factor): + enhancer = ImageEnhance.Contrast(img) + return enhancer.enhance(factor) + + def add_noise(img, intensity): + img_array = np.array(img).astype(np.float32) + noise = np.random.normal(0, intensity * 255, img_array.shape) + noisy_array = np.clip(img_array + noise, 0, 255).astype(np.uint8) + return Image.fromarray(noisy_array) + + def blur_image(img, radius): + return img.filter(ImageFilter.GaussianBlur(radius=radius)) + + transforms.append(("rotate", lambda img: rotate_image(img, random.uniform(-params["rotation_max"], params["rotation_max"])))) + transforms.append(("brightness", lambda img: adjust_brightness(img, random.uniform(1.0-params["brightness_var"], 1.0+params["brightness_var"])))) + transforms.append(("contrast", lambda img: adjust_contrast(img, random.uniform(1.0-params["contrast_var"], 1.0+params["contrast_var"])))) + transforms.append(("noise", lambda img: add_noise(img, random.uniform(params["noise_min"], params["noise_max"])))) + transforms.append(("blur", lambda img: blur_image(img, random.uniform(params["blur_min"], params["blur_max"])))) + + transforms.append(("washboard", lambda img: washboard_warp(img, amplitude=random.uniform(6, 12), wavelength=random.uniform(90, 150), phase=random.uniform(0, 2*pi), decay_from_top=random.choice([True, False])))) + transforms.append(("cylinder", lambda img: cylindrical_edge_warp(img, side=random.choice(["left", "right"]), strength=random.uniform(0.4, 0.8) * random.choice([1, -1]), warp_portion=random.uniform(0.35, 0.5)))) + + if params.get('enable_advanced_effects', True): + if random.random() < params.get('fold_probability', 0.4): + transforms.append(("fold_crease", lambda img: Image.fromarray( + AdvancedImageEffects.simulate_fold_crease(np.array(img), generate_random_fold_lines(img.size), params.get("fold_intensity", 0.3)) + ))) + if random.random() < params.get('advanced_effect_probability', 0.7): + transforms.append(("ink_bleed", lambda img: Image.fromarray( + AdvancedImageEffects.simulate_ink_bleed(np.array(img), params.get("bleed_intensity", 0.3), params.get("bleed_radius", 3)) + ))) + if random.random() < params.get('perspective_probability', 0.5): + transforms.append(("perspective", lambda img: Image.fromarray( + AdvancedImageEffects.apply_perspective_distortion(np.array(img), params.get("corner_displacement", 20)) + ))) + if random.random() < params.get('shadow_probability', 0.6): + transforms.append(("shadow_cast", lambda img: Image.fromarray( + AdvancedImageEffects.apply_shadow_effects(np.array(img), params.get("shadow_angle", 45), params.get("shadow_intensity", 0.4)) + ))) + if random.random() < params.get('advanced_effect_probability', 0.7): + transforms.append(("morphological", lambda img: Image.fromarray( + AdvancedImageEffects.apply_morphological_operations(np.array(img), params.get("morph_operation", "mixed"), params.get("morph_kernel_size", 3)) + ))) + if params.get('scanner_artifacts', True) and random.random() < 0.3: + transforms.append(("scanner_artifacts", lambda img: Image.fromarray( + AdvancedImageEffects.simulate_scanner_artifacts(np.array(img), params.get("compression_quality", 85)) + ))) + if random.random() < 0.3: + transforms.append(("lens_distortion", lambda img: Image.fromarray( + AdvancedImageEffects.apply_lens_distortion(np.array(img), params.get("lens_distortion_strength", 0.2)) + ))) + + if params["all_transforms"]: + selected_transforms = transforms + else: + n_transforms = random.randint(1, min(5, len(transforms))) + selected_transforms = random.sample(transforms, n_transforms) + + for transform_name, transform_func in selected_transforms: + try: + transformed_img = safe_apply_effect(transform_func, original_image, transform_name) + if output_dir: + transformed_filename = f"{base_filename}_{transform_name}.png" + transformed_path = os.path.join(output_dir, transformed_filename) + transformed_img.save(transformed_path) + logger.info(f"Saved transformed image to {transformed_path}") + all_images.append(transformed_img) + except Exception as e: + logger.error(f"Error applying transform {transform_name}: {e}") + + if len(selected_transforms) > 1: + try: + combined_img = original_image.copy() + for _, transform_func in selected_transforms: + combined_img = safe_apply_effect(transform_func, combined_img, "combined") + if output_dir: + combined_path = os.path.join(output_dir, f"{base_filename}_combined.png") + combined_img.save(combined_path) + logger.info(f"Saved combined transformation to {combined_path}") + all_images.append(combined_img) + except Exception as e: + logger.error(f"Error creating combined transformation: {e}") + + return all_images + + +def create_comprehensive_effect_combinations(): + base_effects = ["rotate", "brightness", "contrast", "noise", "blur"] + geometric_effects = ["washboard", "cylinder"] + advanced_effects = [ + "fold_crease", + "ink_bleed", + "perspective", + "shadow_cast", + "morphological", + "scanner_artifacts", + "lens_distortion", + ] + effect_combinations = [] + for effect in base_effects + geometric_effects + advanced_effects: + effect_combinations.append([effect]) + for combo in itertools.combinations(advanced_effects, 2): + effect_combinations.append(list(combo)) + for geo in geometric_effects: + for adv in advanced_effects: + effect_combinations.append([geo, adv]) + for combo in itertools.combinations(advanced_effects, 3): + effect_combinations.append(list(combo)) + effect_combinations.append(advanced_effects[:4]) + effect_combinations.append(advanced_effects[4:]) + effect_combinations.append(advanced_effects) + return effect_combinations + + +def apply_systematic_postprocessing(original_image: Image.Image, output_dir: str, base_filename: str, params: Dict, effect_combination: List[str] = None) -> List[Image.Image]: + all_images = [original_image] + + def rotate_image(img, angle): + bg_color = tuple(np.array(img).mean(axis=(0, 1)).astype(int)) + return img.rotate(angle, resample=Image.BICUBIC, expand=False, fillcolor=bg_color) + + def adjust_brightness(img, factor): + enhancer = ImageEnhance.Brightness(img) + return enhancer.enhance(factor) + + def adjust_contrast(img, factor): + enhancer = ImageEnhance.Contrast(img) + return enhancer.enhance(factor) + + def add_noise(img, intensity): + img_array = np.array(img).astype(np.float32) + noise = np.random.normal(0, intensity * 255, img_array.shape) + noisy_array = np.clip(img_array + noise, 0, 255).astype(np.uint8) + return Image.fromarray(noisy_array) + + def blur_image(img, radius): + return img.filter(ImageFilter.GaussianBlur(radius=radius)) + + transforms = { + "rotate": lambda img: rotate_image(img, random.uniform(-params["rotation_max"], params["rotation_max"])), + "brightness": lambda img: adjust_brightness(img, random.uniform(1.0-params["brightness_var"], 1.0+params["brightness_var"])), + "contrast": lambda img: adjust_contrast(img, random.uniform(1.0-params["contrast_var"], 1.0+params["contrast_var"])), + "noise": lambda img: add_noise(img, random.uniform(params["noise_min"], params["noise_max"])), + "blur": lambda img: blur_image(img, random.uniform(params["blur_min"], params["blur_max"])) , + "washboard": lambda img: washboard_warp(img, amplitude=random.uniform(6, 12), wavelength=random.uniform(90, 150), phase=random.uniform(0, 2*pi), decay_from_top=random.choice([True, False])), + "cylinder": lambda img: cylindrical_edge_warp(img, side=random.choice(["left", "right"]), strength=random.uniform(0.4, 0.8) * random.choice([1, -1]), warp_portion=random.uniform(0.35, 0.5)), + "fold_crease": lambda img: Image.fromarray(AdvancedImageEffects.simulate_fold_crease(np.array(img), generate_random_fold_lines(img.size), params.get("fold_intensity", 0.3))), + "ink_bleed": lambda img: Image.fromarray(AdvancedImageEffects.simulate_ink_bleed(np.array(img), params.get("bleed_intensity", 0.3), params.get("bleed_radius", 3))), + "perspective": lambda img: Image.fromarray(AdvancedImageEffects.apply_perspective_distortion(np.array(img), params.get("corner_displacement", 20))), + "shadow_cast": lambda img: Image.fromarray(AdvancedImageEffects.apply_shadow_effects(np.array(img), params.get("shadow_angle", 45), params.get("shadow_intensity", 0.4))), + "morphological": lambda img: Image.fromarray(AdvancedImageEffects.apply_morphological_operations(np.array(img), params.get("morph_operation", "mixed"), params.get("morph_kernel_size", 3))), + "scanner_artifacts": lambda img: Image.fromarray(AdvancedImageEffects.simulate_scanner_artifacts(np.array(img), params.get("compression_quality", 85))), + "lens_distortion": lambda img: Image.fromarray(AdvancedImageEffects.apply_lens_distortion(np.array(img), params.get("lens_distortion_strength", 0.2))), + } + + current_image = original_image + for effect_name in ["rotate", "brightness", "contrast", "noise", "blur"]: + current_image = safe_apply_effect(transforms[effect_name], current_image, effect_name) + + if effect_combination: + for effect_name in effect_combination: + if effect_name in transforms: + current_image = safe_apply_effect(transforms[effect_name], current_image, effect_name) + if output_dir: + combo_name = "_".join(effect_combination) + filename = f"{base_filename}_{combo_name}.png" + filepath = os.path.join(output_dir, filename) + current_image.save(filepath) + logger.info(f"Saved combination image: {filepath}") + + all_images.append(current_image) + return all_images + diff --git a/backend/data/annotations/annotations.csv b/backend/data/annotations/annotations.csv new file mode 100644 index 0000000000000000000000000000000000000000..d6e2865f439c69f9acaa52cd696d8294044e5983 --- /dev/null +++ b/backend/data/annotations/annotations.csv @@ -0,0 +1,22 @@ +image_filename,extracted_text,validated_text +s1.png,"ଉପକ୍ରମ [ ସମ୍ପାଦନା ] +ବିଜ୍ଞାନ ହେଉଛି ଏକ ସୁବ୍ୟବସ୍ଥିତ ପ୍ରଣାଳୀ ଯାହାଦ୍ବାରା ବିଶ୍ବର ସମସ୍ତ ପଦାର୍ଥ ଓ ବସ୍ତୁମାନଙ୍କ ବିଷୟରେ ଆଲୋଚନା, ପରୀକ୍ଷା, +ଗବେଷଣା, ଅନୁମାନ ଏବଂ ପଠନ କରାଯାଇଥାଏ । ଆଧୁନିକ ଯୁଗରେ ବିଜ୍ଞାନ କହିଲେ ସାଧାରଣତଃ କିଛି ଶିଖୁବା ଶୈଳୀକୁ +ବୁଝାଇଥାଏ","ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ, +ନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ +ଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ +ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ । ୪୪" +s1_1.png,"ଉପକ୍ରମ [ ସମ୍ପାଦନା ] +ବିଜ୍ଞାନ ହେଉଛି ଏକ ସୁବ୍ୟବସ୍ଥିତ ପ୍ରଣାଳୀ ଯାହାଦ୍ବାରା ବିଶ୍ବର ସମସ୍ତ ପଦାର୍ଥ ଓ ବସ୍ତୁମାନଙ୍କ ବିଷୟରେ ଆଲୋଚନା, ପରୀକ୍ଷା, +ଗବେଷଣା, ଅନୁମାନ ଏବଂ ପଠନ କରାଯାଇଥାଏ । ଆଧୁନିକ ଯୁଗରେ ବିଜ୍ଞାନ କହିଲେ ସାଧାରଣତଃ କିଛି ଶିଖୁବା ଶୈଳୀକୁ +ବୁଝାଇଥାଏ","ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ, +ନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ +ଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ +ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ । ୪୪" +s2.png,"ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ, +ନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ +ଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ +ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ ।","ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ, +ନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ +ଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ +ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ । ୪୪" diff --git a/backend/data/annotations/annotations.json b/backend/data/annotations/annotations.json new file mode 100644 index 0000000000000000000000000000000000000000..193c285b81e517225444e2b60668d788d4d37f1c --- /dev/null +++ b/backend/data/annotations/annotations.json @@ -0,0 +1,10 @@ +{ + "s1.png": { + "extracted_text": "ଉପକ୍ରମ [ ସମ୍ପାଦନା ]\nବିଜ୍ଞାନ ହେଉଛି ଏକ ସୁବ୍ୟବସ୍ଥିତ ପ୍ରଣାଳୀ ଯାହାଦ୍ବାରା ବିଶ୍ବର ସମସ୍ତ ପଦାର୍ଥ ଓ ବସ୍ତୁମାନଙ୍କ ବିଷୟରେ ଆଲୋଚନା, ପରୀକ୍ଷା,\nଗବେଷଣା, ଅନୁମାନ ଏବଂ ପଠନ କରାଯାଇଥାଏ । ଆଧୁନିକ ଯୁଗରେ ବିଜ୍ଞାନ କହିଲେ ସାଧାରଣତଃ କିଛି ଶିଖୁବା ଶୈଳୀକୁ\nବୁଝାଇଥାଏ", + "validated_text": "ଉପକ୍ରମ [ ସମ୍ପାଦନା ] ୩୩\nବିଜ୍ଞାନ ହେଉଛି ଏକ ସୁବ୍ୟବସ୍ଥିତ ପ୍ରଣାଳୀ ଯାହାଦ୍ବାରା ବିଶ୍ବର ସମସ୍ତ ପଦାର୍ଥ ଓ ବସ୍ତୁମାନଙ୍କ ବିଷୟରେ ଆଲୋଚନା, ପରୀକ୍ଷା,\nଗବେଷଣା, ଅନୁମାନ ଏବଂ ପଠନ କରାଯାଇଥାଏ । ଆଧୁନିକ ଯୁଗରେ ବିଜ୍ଞାନ କହିଲେ ସାଧାରଣତଃ କିଛି ଶିଖୁବା ଶୈଳୀକୁ\nବୁଝାଇଥାଏ" + }, + "s2.png": { + "extracted_text": "ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ,\nନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ\nଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ\nଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ ।", + "validated_text": "ଧର୍ମ ହେଉଛି ନିର୍ଦ୍ଦିଷ୍ଟ ଆଚରଣ ଏବଂ ଅଭ୍ୟାସ, ବିଶ୍ବ ଦୃଷ୍ଟିକୋଣ, ଦିବ୍ୟଗ୍ରନ୍ଥ, ତୀର୍ଥ, ଭବିଷ୍ୟବାଣୀ,\nନୈତିକତା ଇତ୍ୟାଦି ଯାହା ମାନବକୁ ଆଲୌକିକ, ଆଧ୍ୟୟନିକ କିମ୍ବା ଆଧ୍ୟାତ୍ମିକ ଉପାଦାନ ସହିତ\nଜଡ଼ିତ କରୁଥିବା ସାମାଜିକ-ସାଂସ୍କୃତିକ ବ୍ୟବସ୍ଥା । ତେବେ ସୃଷ୍ଟ ଭାବରେ ଧର୍ମର ସଜ୍ଞା ଉପରେ\nଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଅଧ୍ୟୟନପ୍ରସୂତ ସହମତି ନାହିଁ । ୪୪" + } +} \ No newline at end of file diff --git a/backend/data/uploaded_images/s1.png b/backend/data/uploaded_images/s1.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb615693a69f8a9530a4be0614454d41d72fedc Binary files /dev/null and b/backend/data/uploaded_images/s1.png differ diff --git a/backend/data/uploaded_images/s1_1.png b/backend/data/uploaded_images/s1_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb615693a69f8a9530a4be0614454d41d72fedc Binary files /dev/null and b/backend/data/uploaded_images/s1_1.png differ diff --git a/backend/data/uploaded_images/s2.png b/backend/data/uploaded_images/s2.png new file mode 100644 index 0000000000000000000000000000000000000000..f19ebde427883f8529a0043d5ece805a9d122682 Binary files /dev/null and b/backend/data/uploaded_images/s2.png differ diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c6ddf5c6434ac69846440890458e9d3d2109e30f --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,17 @@ +fastapi +uvicorn +python-multipart +pydantic +google-api-core>=2.0.0 +google-generativeai + +# Image/text generation stack +opencv-python +pillow +numpy +scipy +noise +pandas +requests +matplotlib +datasets diff --git a/content/static/NotoSansOriya-Black (2).ttf b/content/static/NotoSansOriya-Black (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..9f166e12c2d4fbbbbff1f65e206677c2391746aa --- /dev/null +++ b/content/static/NotoSansOriya-Black (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb33fbf1d96373a315468ba4087645cac7fbf3b7f0da9cc5a7fb8d6bbc79f7e3 +size 142472 diff --git a/content/static/NotoSansOriya-Black.ttf b/content/static/NotoSansOriya-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9f166e12c2d4fbbbbff1f65e206677c2391746aa --- /dev/null +++ b/content/static/NotoSansOriya-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb33fbf1d96373a315468ba4087645cac7fbf3b7f0da9cc5a7fb8d6bbc79f7e3 +size 142472 diff --git a/content/static/NotoSansOriya-Bold (2).ttf b/content/static/NotoSansOriya-Bold (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..10a753a1ee495c2f30b74fd633c2e81f74635d6d --- /dev/null +++ b/content/static/NotoSansOriya-Bold (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b904cd4edafaa595e5ba0e76094503b5f9a07967360c32013de63b7b9318e45f +size 155216 diff --git a/content/static/NotoSansOriya-Bold.ttf b/content/static/NotoSansOriya-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..10a753a1ee495c2f30b74fd633c2e81f74635d6d --- /dev/null +++ b/content/static/NotoSansOriya-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b904cd4edafaa595e5ba0e76094503b5f9a07967360c32013de63b7b9318e45f +size 155216 diff --git a/content/static/NotoSansOriya-ExtraBold (2).ttf b/content/static/NotoSansOriya-ExtraBold (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..d24955532c2d1655ee1e614fba1ac0d1c70f18ec --- /dev/null +++ b/content/static/NotoSansOriya-ExtraBold (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da1f187e6c30d6931ffbee9b144b1396568d68273542b24f80844bfa12a408d4 +size 142676 diff --git a/content/static/NotoSansOriya-ExtraBold.ttf b/content/static/NotoSansOriya-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d24955532c2d1655ee1e614fba1ac0d1c70f18ec --- /dev/null +++ b/content/static/NotoSansOriya-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da1f187e6c30d6931ffbee9b144b1396568d68273542b24f80844bfa12a408d4 +size 142676 diff --git a/content/static/NotoSansOriya-ExtraLight (2).ttf b/content/static/NotoSansOriya-ExtraLight (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..d55f966db81137dab6735456930ebc97500ec072 --- /dev/null +++ b/content/static/NotoSansOriya-ExtraLight (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c82202255f5b0ceee121dd00ed9fdccf03066283e5774a2e256fdb21c88292bc +size 155120 diff --git a/content/static/NotoSansOriya-ExtraLight.ttf b/content/static/NotoSansOriya-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d55f966db81137dab6735456930ebc97500ec072 --- /dev/null +++ b/content/static/NotoSansOriya-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c82202255f5b0ceee121dd00ed9fdccf03066283e5774a2e256fdb21c88292bc +size 155120 diff --git a/content/static/NotoSansOriya-Light (2).ttf b/content/static/NotoSansOriya-Light (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..4bec4c1bbeeb61d31953d9bc540d4edbeedcbd82 --- /dev/null +++ b/content/static/NotoSansOriya-Light (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffbf720886730b128dc4d0fb333d7741b22ca36a56d989cdfc7e90002249f88d +size 155164 diff --git a/content/static/NotoSansOriya-Light.ttf b/content/static/NotoSansOriya-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4bec4c1bbeeb61d31953d9bc540d4edbeedcbd82 --- /dev/null +++ b/content/static/NotoSansOriya-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffbf720886730b128dc4d0fb333d7741b22ca36a56d989cdfc7e90002249f88d +size 155164 diff --git a/content/static/NotoSansOriya-Medium (2).ttf b/content/static/NotoSansOriya-Medium (2).ttf new file mode 100644 index 0000000000000000000000000000000000000000..90a40b86e4bded29a953fe2e8a43ace16cebbfa8 --- /dev/null +++ b/content/static/NotoSansOriya-Medium (2).ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e0457c076cdb02f963737648a248ff74c3f3ac29aca2d2e5c3266b9d163fb22 +size 155148 diff --git a/content/static/NotoSansOriya-Medium.ttf b/content/static/NotoSansOriya-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..90a40b86e4bded29a953fe2e8a43ace16cebbfa8 --- /dev/null +++ b/content/static/NotoSansOriya-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e0457c076cdb02f963737648a248ff74c3f3ac29aca2d2e5c3266b9d163fb22 +size 155148 diff --git a/content/static/NotoSansOriya-Regular.ttf b/content/static/NotoSansOriya-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..33cfe3a3a852cc0311ea429676fe6310c3c5ff53 --- /dev/null +++ b/content/static/NotoSansOriya-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5d16377ee01703170468402ad02d15595c77150bf62b4c31c66d3e79ad58039 +size 154960 diff --git a/content/static/NotoSansOriya-SemiBold.ttf b/content/static/NotoSansOriya-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ede41d216762d1369cb00664ed80c9058f7fd909 --- /dev/null +++ b/content/static/NotoSansOriya-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e86666b102c911434003af8b93442f2136f19fceab56ba940473a76c28d6801 +size 155176 diff --git a/content/static/NotoSansOriya-Thin.ttf b/content/static/NotoSansOriya-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a3cc7b0d4f57ea078de1868bebc19e93205eb630 --- /dev/null +++ b/content/static/NotoSansOriya-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d0fc04ba3330cca8d95d644a78829ad32195488df574aae4b18e09743023409 +size 154372 diff --git a/content/static/NotoSansOriya_Condensed-Black.ttf b/content/static/NotoSansOriya_Condensed-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7f84c3d1d9aebf4d4a7199f8b29c98cfaf223200 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec2f25042a7500ecbb04b5b4672a7e6549b3d837a44fe9799012b82f1740709a +size 142696 diff --git a/content/static/NotoSansOriya_Condensed-Bold.ttf b/content/static/NotoSansOriya_Condensed-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a36512f807998a6d89b198d4fa85eed9146f4a75 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e9d953ba9a0055476c14a688073ed4fd91ebfd0bdaad9c83ff2392a30463d17 +size 155080 diff --git a/content/static/NotoSansOriya_Condensed-ExtraBold.ttf b/content/static/NotoSansOriya_Condensed-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..955a95c649700bdd513a62fdb9491ffdc7e50a6c --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77cd61a8ae71154ee5076fd872854a60e2468c9777fe4b62b391ebd080ff9931 +size 142852 diff --git a/content/static/NotoSansOriya_Condensed-ExtraLight.ttf b/content/static/NotoSansOriya_Condensed-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9da3b393a6c31de8af11aa04d77b4a4d98c0e374 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8275fdea5c4a89380e5139d3cfafa43cc6f9f38d98cc4dc19a0386382f3ec65f +size 154960 diff --git a/content/static/NotoSansOriya_Condensed-Light.ttf b/content/static/NotoSansOriya_Condensed-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e9a0dda289945e9170d960659d27fc623f365d3a --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d6e58df83c60b8aaff9652f8c1e728f6db5fd559d0c4ef3ba940d32c097ed32 +size 154964 diff --git a/content/static/NotoSansOriya_Condensed-Medium.ttf b/content/static/NotoSansOriya_Condensed-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9aa6be379c839a93ac8c7e3f998877f75c1fc3f0 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f8e25c863a076f16b414027d92a36b3c8ae879421548013e8bfdc214671bc1b +size 155092 diff --git a/content/static/NotoSansOriya_Condensed-Regular.ttf b/content/static/NotoSansOriya_Condensed-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7896f634f02ab5ddfbc23937c916a9af4b917f89 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac6263cb259109278a7a4f94ed5dffa6f5b68755304fe1812912eff7d31dc332 +size 154976 diff --git a/content/static/NotoSansOriya_Condensed-SemiBold.ttf b/content/static/NotoSansOriya_Condensed-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..feb89f3dd338d1476c797f0db654d2306d8dcd96 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0e563557bc6c181cf446eaa1ddf40ec3e7f06a22085a8d1833cf6e1499c7ef9 +size 155152 diff --git a/content/static/NotoSansOriya_Condensed-Thin.ttf b/content/static/NotoSansOriya_Condensed-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d96c1e487ac876b60b3aa5be562b17bff0a345b6 --- /dev/null +++ b/content/static/NotoSansOriya_Condensed-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82faf95ee90bd0b5cc56faba1fa888ae30b9984983b00c77b6c0157ba1df35d5 +size 154340 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Black.ttf b/content/static/NotoSansOriya_ExtraCondensed-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..287df540f623d710d75cedbb2433b980b97cf25a --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ab791dee9aeb1cd0bb66d98f3387e51ef2c279863c9af207a6048f34fa433e9 +size 142288 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Bold.ttf b/content/static/NotoSansOriya_ExtraCondensed-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..282b043c990a68c3b665046fbbffa9af2696c146 --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b8ec663c3c038aa2e0e8f9c4cb6530d8b390d6610bb47f8064abdc8c0f00ebe +size 154828 diff --git a/content/static/NotoSansOriya_ExtraCondensed-ExtraBold.ttf b/content/static/NotoSansOriya_ExtraCondensed-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2fa5ca87c7bfe1c6717b4565d218694882aeeb7e --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:536dcb3da1f946d0ba445327b4f3dc91762ebdfa71dd3edfe8fca8fba60171d7 +size 142396 diff --git a/content/static/NotoSansOriya_ExtraCondensed-ExtraLight.ttf b/content/static/NotoSansOriya_ExtraCondensed-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2ebfdedf5db291ee5e933b4bd06369d2d02a72c6 --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d6b7724790a4e31509ccf5f9554be37709246c5060bce71248a649b78878402 +size 154688 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Light.ttf b/content/static/NotoSansOriya_ExtraCondensed-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dc36b70a91a9bf1ba791f9e27df36b55e111949e --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a50bddfb4168f880fb6af8de6a0dff2fb7cf7307544d17d19386ee14b9c85478 +size 154640 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Medium.ttf b/content/static/NotoSansOriya_ExtraCondensed-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4bcca8ef628ed152451339527f4c26166699c70b --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58385542c4ffc235d418479290c9b63d5a5b2a903d30376f0109d727935ae297 +size 154796 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Regular.ttf b/content/static/NotoSansOriya_ExtraCondensed-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..39a0f2dfe6c2fbdaa5662eed72252141e012cb3c --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:497a9a21aabd6fbaa610f1660628262d6afd8e841da4232ff51ed8cdbf38a052 +size 154700 diff --git a/content/static/NotoSansOriya_ExtraCondensed-SemiBold.ttf b/content/static/NotoSansOriya_ExtraCondensed-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9baf70af1f7d914faef9d9393883fed8666f624d --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:874bbca36e1f610d9d6c4e2f917cfd2f2d92c366d9f3940e67ff3816b58dd419 +size 154840 diff --git a/content/static/NotoSansOriya_ExtraCondensed-Thin.ttf b/content/static/NotoSansOriya_ExtraCondensed-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b50a8b610a22ded79fd7ba6891024edc8e632b38 --- /dev/null +++ b/content/static/NotoSansOriya_ExtraCondensed-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9519028e6c7dacad49c3d1b86aeaf85fc847d09ccd5b3f432ee3a375643c3057 +size 153932 diff --git a/content/static/NotoSansOriya_SemiCondensed-Black.ttf b/content/static/NotoSansOriya_SemiCondensed-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b7ebfa75038f203fc8fd1e73feab7af2eb1a16c4 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54ff4ffdeea0b50f5030aab6cbe19529f1d8baecd31359f14a853ff38dd3fc4b +size 142676 diff --git a/content/static/NotoSansOriya_SemiCondensed-Bold.ttf b/content/static/NotoSansOriya_SemiCondensed-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6740508dc1662e92de52767afb80b38ff1f0d1c3 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47dea246b016c96b6a68d1a51294fc851456dfadc9e4e44aae2181d450b86336 +size 155268 diff --git a/content/static/NotoSansOriya_SemiCondensed-ExtraBold.ttf b/content/static/NotoSansOriya_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..03f1ba3213288c3aa0958bef2a111c742c26ef59 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f70fd555b8a9d3bb8a038dc3802065c5717c780656c73c975abfcceced768d8 +size 142836 diff --git a/content/static/NotoSansOriya_SemiCondensed-ExtraLight.ttf b/content/static/NotoSansOriya_SemiCondensed-ExtraLight.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1574f76944216388a8cd4aca6bd133a817fe92b6 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e067f77398535751d42842efd0089ef0a289758cd3c45bf91e4152a63938649 +size 155096 diff --git a/content/static/NotoSansOriya_SemiCondensed-Light.ttf b/content/static/NotoSansOriya_SemiCondensed-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..275d4a1fc65a00ef2b58396a055c567d06a8a1a2 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d0a953d613f64fb9c7c3c264a8c34b13bef91d60b0b1550bd47200d26718878 +size 155252 diff --git a/content/static/NotoSansOriya_SemiCondensed-Medium.ttf b/content/static/NotoSansOriya_SemiCondensed-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..adeea30e090022490367b6031c02d0cd1571fb97 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48eaf8f7b86db462326c0e5348c9d5fa07e86661be6048bd57a654fc777f55da +size 155256 diff --git a/content/static/NotoSansOriya_SemiCondensed-Regular.ttf b/content/static/NotoSansOriya_SemiCondensed-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..28db148df8553258d302701df2b39d0396ca2aba --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a223f899001d52471104b141d0ef9ae51923215e178f124224ad582a3b64024 +size 155176 diff --git a/content/static/NotoSansOriya_SemiCondensed-SemiBold.ttf b/content/static/NotoSansOriya_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..54fea726e9646734529a50b45d3ca1e910766ab0 --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9111b9350280dfae52b7774182e0592af9987539f7d539253699bc756077d481 +size 155292 diff --git a/content/static/NotoSansOriya_SemiCondensed-Thin.ttf b/content/static/NotoSansOriya_SemiCondensed-Thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7abf4e1142755bc20b3cd6f9e84eb1dffa67ea6a --- /dev/null +++ b/content/static/NotoSansOriya_SemiCondensed-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbcfc4563d290d987848e32ef8a34fb0d1e41754734fda72d5282f55a74822be +size 154484 diff --git a/frontend/.env.local b/frontend/.env.local new file mode 100644 index 0000000000000000000000000000000000000000..643047f42ac1ec80f9406061cfedc0203a3c0a5a --- /dev/null +++ b/frontend/.env.local @@ -0,0 +1 @@ +VITE_API_BASE=http://localhost:8000 \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..82eaa85ce3a5c1c7a31713d4c1eb173b55058a93 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Allow yarn.lock +!yarn.lock \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000000000000000000000000000000000000..38634af45f2fdaf7cb676743d0cd447e08a510f8 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + + Odiya OCR Annotation Tool + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..7879b228969d7164f5f4ac9196098f492083a737 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3886 @@ +{ + "name": "vite-tailwind-app", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite-tailwind-app", + "version": "0.0.1", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@mui/material": "^7.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.6.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.24", + "tailwindcss": "^3.3.2", + "vite": "^6.3.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz", + "integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz", + "integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/core-downloads-tracker": "^7.1.0", + "@mui/system": "^7.1.0", + "@mui/types": "^7.4.2", + "@mui/utils": "^7.1.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.1.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz", + "integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/utils": "^7.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz", + "integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz", + "integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/private-theming": "^7.1.0", + "@mui/styled-engine": "^7.1.0", + "@mui/types": "^7.4.2", + "@mui/utils": "^7.1.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz", + "integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz", + "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/types": "^7.4.2", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", + "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", + "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz", + "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==", + "license": "MIT", + "dependencies": { + "react-router": "7.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..b80d3c6b82fd142f8dbf8683ecd618f1579081f2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "vite-tailwind-app", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@mui/material": "^7.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.6.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.0", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.24", + "tailwindcss": "^3.3.2", + "vite": "^6.3.5" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..2e7af2b7f1a6f391da1631d93968a9d487ba977d --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/Odia_gen_ai.svg b/frontend/public/Odia_gen_ai.svg new file mode 100644 index 0000000000000000000000000000000000000000..2de27e13156076df0ab485b10d7553e6a37182fe --- /dev/null +++ b/frontend/public/Odia_gen_ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e185d15d053cce73a0c1dd3ec45694d145c360e9 --- /dev/null +++ b/frontend/public/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd2da6a457f7bd93e79e43faaf891d3cc559d5ca85d29b3c51da53317f169fd2 +size 214250 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b8ec4c142fe8db01308c6a972b75705a05b825f5 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import { BrowserRouter, Routes, Route, NavLink, Navigate } from "react-router-dom"; +import OdiyaOCRTool from "./OdiyaOCRTool"; +import Synthetic from "./pages/Synthetic"; + +const Nav = () => ( +
+
+
+ Logo + Odia AI Tools +
+
+ + `px-3 py-1 rounded ${isActive ? "bg-blue-600 text-white" : "hover:bg-gray-100"}` + } + > + OCR Annotation + + + `px-3 py-1 rounded ${isActive ? "bg-blue-600 text-white" : "hover:bg-gray-100"}` + } + > + Synthetic Generator + +
+
+
+); + +const App = () => { + return ( + +