` | Display expected program output | +| `NetworkDiagram` | `react-flow` or Mermaid | Display network topology diagrams for networking questions | + +### 12.4 Shared Components + +| Component | Purpose | +|-----------|---------| +| `MasteryBar` | Horizontal bar showing mastery level with color coding (grey/red/orange/yellow/green) | +| `DomainRadar` | Radar chart showing mastery across all domains in a subject | +| `TopicCard` | Card showing topic name, mastery, status (locked/available/completed), prerequisite indicator | +| `PlanTimeline` | Vertical timeline of learning plan items with status icons | +| `QuizTimer` | Countdown timer for mastery quizzes (reuse from existing exam timer) | +| `ProgressRing` | Circular progress indicator for overall subject mastery | +| `ResourceCard` | Card for learning resources with type icon, title, estimated time, completion checkbox | +| `CoachBubble` | Chat-like bubble for AI coaching responses | + +--- + +## 13. Non-Functional Requirements + +### 13.1 Performance + +| Metric | Target | +|--------|--------| +| Diagnostic question generation | < 3 seconds per question | +| Practice question generation (batch of 5) | < 5 seconds | +| AI grading (per answer) | < 5 seconds for auto-gradeable, < 10 seconds for AI-graded | +| Learning plan generation | < 10 seconds | +| AI content generation (explanation) | < 15 seconds | +| AI coaching response (hint/explanation) | < 5 seconds | +| Resource file upload | < 30 seconds for files up to 50 MB | +| Proficiency profile load | < 1 second | +| Taxonomy tree load | < 2 seconds | + +### 13.2 Scalability + +| Dimension | Target | +|-----------|--------| +| Concurrent students per subject | 200 | +| Total topics per subject | 500 | +| Total resources per subject | 1,000 | +| AI content cache entries | 10,000 | +| Proficiency records | 200 students x 500 topics = 100,000 records | + +### 13.3 Data Integrity + +- Proficiency scores are never overwritten -- all changes are additive (new assessment results update the score via weighted average, never direct replacement) +- Learning plan changes are logged (who changed, when, what was before/after) +- AI-generated content is cached and versioned; regeneration creates a new version, does not delete the old one +- Resource deletions are soft-deletes (is_active = False) to preserve student completion records + +### 13.4 Security + +- All endpoints require JWT authentication (via existing `_authenticate()`) +- Students can only access their own proficiency and learning plan data +- Teachers can view (but not modify without explicit override) student data in their classrooms +- Admin has full access +- Resource uploads are scanned for file type validation (no executable uploads) +- AI prompts never include student personal data (only anonymized performance metrics) + +--- + +## Appendix A: Implementation Priority + +| Priority | Component | Rationale | +|----------|-----------|-----------| +| **P0** | Subject taxonomy models + CRUD API | Foundation for everything else | +| **P0** | Resource model + upload API | Staff needs to start uploading content immediately | +| **P1** | Diagnostic assessment engine + UI | Entry point for students | +| **P1** | Proficiency profile model + API | Required by learning plan | +| **P1** | Learning plan generation + UI | Core student experience | +| **P2** | Content delivery (resource viewer + AI content) | Content consumption | +| **P2** | Practice questions + grading | Practice before mastery quiz | +| **P2** | Mastery quiz + progression | Advancement mechanism | +| **P3** | AI coaching (hints, explanations) | Enhancement to learning experience | +| **P3** | Spaced repetition | Long-term retention | +| **P3** | Analytics dashboards | Monitoring and optimization | +| **P4** | Content gap detection | Operational efficiency | +| **P4** | FAISS training tips per subject | Advanced personalization | + +## Appendix B: Dependency Graph for Implementation + +```mermaid +graph TB + TaxModels["P0: Taxonomy Models
(Subject, Domain, Topic, Objective)"] --> TaxAPI["P0: Taxonomy CRUD API"] + TaxModels --> ResModels["P0: Resource Model"] + ResModels --> ResAPI["P0: Resource Upload API"] + + TaxAPI --> DiagEngine["P1: Diagnostic Engine"] + TaxModels --> ProfModel["P1: Proficiency Model"] + DiagEngine --> ProfModel + ProfModel --> PlanGen["P1: Learning Plan Generation"] + TaxModels --> PlanGen + + ResAPI --> ContentDel["P2: Content Delivery"] + PlanGen --> ContentDel + ContentDel --> Practice["P2: Practice Questions"] + Practice --> MasteryQuiz["P2: Mastery Quiz"] + MasteryQuiz --> ProfModel + + Practice --> Coaching["P3: AI Coaching"] + MasteryQuiz --> SpacedRep["P3: Spaced Repetition"] + ProfModel --> Analytics["P3: Analytics"] + + ResAPI --> GapDetect["P4: Content Gap Detection"] + ProfModel --> FAISSTips["P4: FAISS Tips per Subject"] +``` + +--- + +*Document prepared for architect review. All data models, APIs, and specifications are draft and subject to refinement before handoff to the development team.* diff --git a/docs/ODOO_BACKEND_SRS_v3.md b/docs/ODOO_BACKEND_SRS_v3.md new file mode 100644 index 0000000..a6d26c6 --- /dev/null +++ b/docs/ODOO_BACKEND_SRS_v3.md @@ -0,0 +1,747 @@ +# EnCoach Odoo 19 Backend -- Developer SRS v3 + +**Document Version:** 3.0 +**Date:** March 11, 2026 +**Status:** Active -- Ready for Development +**Supersedes:** `ODOO_MIGRATION_SRS_v2.md` (v2.0) +**Master Reference:** `ENCOACH_UNIFIED_SRS.md` (v1.0) +**Author:** EnCoach Architecture Team + +--- + +## Purpose + +This document is the **backend implementation specification** for the Odoo 19 developer. It defines every API endpoint, data model, and module the frontend expects. The frontend is **fully built and wired** -- it makes real HTTP calls to these endpoints via TanStack Query. Your job is to ensure every endpoint listed here exists, returns the correct response shape, and connects to the correct Odoo models. + +**Source of truth for response shapes:** `encoach_frontend_new/src/types/*.ts` (14 files) +**Source of truth for endpoint paths:** `encoach_frontend_new/src/services/*.ts` (21 files) + +--- + +## Table of Contents + +1. [What Already Exists](#1-what-already-exists) +2. [What Needs to Be Built or Modified](#2-what-needs-to-be-built-or-modified) +3. [New Odoo Modules](#3-new-odoo-modules) +4. [Existing Module Modifications](#4-existing-module-modifications) +5. [Complete API Contract](#5-complete-api-contract) +6. [Data Model Specification](#6-data-model-specification) +7. [AI Service Integration](#7-ai-service-integration) +8. [Response Format Standards](#8-response-format-standards) +9. [Non-Functional Requirements](#9-non-functional-requirements) +10. [Implementation Priority](#10-implementation-priority) + +--- + +## 1. What Already Exists + +### 1.1 Existing Odoo Modules (15) + +These modules are built and deployed in `ielts-be-v0/`: + +| # | Module | Controller File | Endpoints | +|---|--------|----------------|-----------| +| 1 | `encoach_core` | -- | Users, entities, roles, permissions, codes, invites | +| 2 | `encoach_ai` | -- | OpenAI service wrapper, constants, blacklist | +| 3 | `encoach_ai_media` | `media.py` | TTS (Polly), STT (Whisper), ELAI avatars | +| 4 | `encoach_ai_generation` | `generation.py` | Exam content generation per module | +| 5 | `encoach_ai_grading` | `grading.py` | Writing/speaking AI grading | +| 6 | `encoach_exam` | `exams.py` | Exam CRUD, approval workflows | +| 7 | `encoach_assignment` | `assignments.py` | Assignment management | +| 8 | `encoach_evaluation` | `evaluations.py` | Evaluation results and AI jobs | +| 9 | `encoach_stats` | `sessions.py`, `stats.py` | Exam sessions and statistics | +| 10 | `encoach_training` | `training.py` | Training content, tips (FAISS), walkthroughs | +| 11 | `encoach_classroom` | `classrooms.py` | Classroom groups | +| 12 | `encoach_subscription` | `subscriptions.py`, `discounts.py` | Packages, payments, discounts | +| 13 | `encoach_registration` | `registration.py` | User registration, batch import | +| 14 | `encoach_ticket` | `tickets.py` | Support tickets | +| 15 | `encoach_api` | `auth.py`, `users.py`, `entities.py`, `storage.py`, `approvals.py` | REST API controllers (81+ endpoints) | + +### 1.2 Existing Endpoint Count by Group + +| Group | Estimated Count | Status | +|-------|----------------|--------| +| Auth (`/api/login`, `/api/logout`, etc.) | 4 | Built | +| Users (`/api/user`, `/api/users/*`) | 6 | Built | +| Entities & Roles (`/api/entities`, `/api/roles`, `/api/permissions`) | 7 | Built | +| Exams (`/api/exam/*`) | 7 | Built | +| AI Generation (`/api/exam/*/generate`) | 12 | Built | +| Media (`/api/exam/*/media`, `/api/transcribe`) | 6 | Built | +| Evaluations (`/api/evaluate/*`, `/api/grading/*`) | 6 | Built | +| Classrooms (`/api/groups`) | 3 | Built | +| Assignments (`/api/assignments`) | 8 | Built | +| Sessions & Stats (`/api/sessions`, `/api/stats`, `/api/statistical`) | 5 | Built | +| Training (`/api/training/*`) | 4 | Built | +| Subscriptions (`/api/packages`, `/api/stripe`, etc.) | 7 | Built | +| Registration (`/api/register`, `/api/code/*`) | 4 | Built | +| Tickets (`/api/tickets`) | 3 | Built | +| Storage (`/api/storage`) | 2 | Built | +| Approvals (`/api/approval-workflows`) | 3 | Built | +| **TOTAL EXISTING** | **~87** | | + +--- + +## 2. What Needs to Be Built or Modified + +### 2.1 New Endpoints Required (Frontend Expects These) + +The frontend calls the following endpoints that **do not exist yet**. You must create them. + +| Group | Count | Priority | +|-------|-------|----------| +| Taxonomy (subjects, domains, topics) | 14 | P0 | +| Adaptive Learning (diagnostic, proficiency, learning plans) | 16 | P0 | +| Resources (upload, manage, review) | 7 | P1 | +| AI Coaching (chat, suggest, explain, tips) | 6 | P1 | +| AI Utilities (search, insights, alerts, reports) | 5 | P2 | +| Analytics (student, class, subject, content gaps) | 4 | P2 | +| LMS Bridge (courses, batches, timetable, attendance, grades) | 13 | P1 | +| **TOTAL NEW** | **~65** | | + +### 2.2 Existing Modules Requiring Modification + +| Module | What to Change | +|--------|---------------| +| `encoach_exam` | Add `subject_id` (Many2one to `encoach.subject`) and `topic_ids` (Many2many to `encoach.topic`) fields to `encoach.exam` model | +| `encoach_stats` | Add `topic_id` and `subject_id` fields to `encoach.stat` model | +| `encoach_training` | Add `subject_id` field to `encoach.training.tip` for per-subject FAISS indices | +| `encoach_ai_generation` | Extend prompts to support Math (include LaTeX/KaTeX formatting) and IT (code blocks, syntax) question types | +| `encoach_ai_grading` | Add grading logic for numerical-tolerance (Math) and keyword/pattern (IT) answers | +| `encoach_subscription` | Add `subjects` field to `encoach.package` for subject-scoped subscriptions | +| `encoach_api` | Add new controller files for all new endpoint groups | + +--- + +## 3. New Odoo Modules + +Create these 8 new modules: + +### 3.1 `encoach_taxonomy` + +**Purpose:** Subject/domain/topic/learning-objective hierarchy. + +**Models:** + +| Model | Key Fields | +|-------|-----------| +| `encoach.subject` | `name` (Char), `code` (Char unique), `is_active` (Boolean), `diagnostic_config` (Text/JSON), `mastery_threshold` (Float default=80), `grading_scale` (Selection: percentage/band/letter), `grading_scale_config` (Text/JSON) | +| `encoach.domain` | `name` (Char), `code` (Char), `subject_id` (M2O encoach.subject), `sequence` (Integer) | +| `encoach.topic` | `name` (Char), `code` (Char), `domain_id` (M2O encoach.domain), `estimated_hours` (Float), `difficulty_level` (Selection: easy/medium/hard), `prerequisite_ids` (M2M self-ref), `question_type_weights` (Text/JSON) | +| `encoach.learning.objective` | `name` (Char), `topic_id` (M2O encoach.topic), `bloom_level` (Selection: remember/understand/apply/analyze/evaluate/create), `sequence` (Integer) | + +**Computed fields on `encoach.subject`:** `domain_count`, `topic_count` +**Computed fields on `encoach.domain`:** `topic_count` +**Computed fields on `encoach.topic`:** `objective_count`, `prerequisite_names` (list of names) + +### 3.2 `encoach_resources` + +**Purpose:** Human-uploaded learning materials tagged to topics. + +**Models:** + +| Model | Key Fields | +|-------|-----------| +| `encoach.resource` | `name` (Char), `resource_type` (Selection: pdf/video/link/document/interactive), `url` (Char), `file` (Binary), `file_name` (Char), `topic_ids` (M2M encoach.topic), `author_id` (M2O res.users), `is_published` (Boolean), `review_status` (Selection: pending/approved/rejected) | +| `encoach.resource.completion` | `student_id` (M2O res.users), `resource_id` (M2O encoach.resource), `viewed` (Boolean), `time_spent_minutes` (Integer), `rating` (Integer 1-5) | + +### 3.3 `encoach_adaptive` + +**Purpose:** Core adaptive learning engine -- proficiency tracking, learning plans, content cache. + +**Models:** + +| Model | Key Fields | +|-------|-----------| +| `encoach.proficiency` | `student_id` (M2O res.users), `topic_id` (M2O encoach.topic), `mastery` (Float 0-100), `mastery_level` (Selection: not_started/beginner/developing/proficient/mastered -- computed from mastery), `last_assessed` (Datetime), `time_spent_minutes` (Integer) | +| `encoach.learning.plan` | `student_id` (M2O res.users), `subject_id` (M2O encoach.subject), `status` (Selection: active/paused/completed), `ai_summary` (Text), `overall_progress` (Float -- computed), `target_completion` (Date) | +| `encoach.learning.plan.item` | `plan_id` (M2O encoach.learning.plan), `topic_id` (M2O encoach.topic), `sequence` (Integer), `status` (Selection: locked/available/in_progress/completed), `estimated_hours` (Float), `actual_hours` (Float), `mastery` (Float -- from proficiency) | +| `encoach.ai.content.cache` | `topic_id` (M2O encoach.topic), `content_type` (Char), `difficulty_level` (Char), `content` (Text/JSON), `model_used` (Char), `review_status` (Selection: auto/reviewed/rejected) | + +**Business logic:** +- `mastery_level` is computed: 0-19 = not_started, 20-39 = beginner, 40-59 = developing, 60-79 = proficient, 80-100 = mastered +- `overall_progress` is computed from item statuses (completed_count / total_count * 100) +- When a plan item is completed, unlock the next item(s) based on prerequisite graph + +### 3.4 `encoach_adaptive_api` + +**Purpose:** REST controllers for the adaptive learning engine. + +**Controller files to create:** +- `taxonomy.py` -- `/api/subjects`, `/api/domains`, `/api/topics`, `/api/subjects/{id}/taxonomy` +- `resources.py` -- `/api/resources` +- `diagnostic.py` -- `/api/diagnostic/start`, `/api/diagnostic/answer`, `/api/diagnostic/{id}/result` +- `proficiency.py` -- `/api/proficiency`, `/api/proficiency/summary`, `/api/proficiency/class` +- `learning_plan.py` -- `/api/learning-plan`, `/api/learning-plan/generate`, etc. +- `content.py` -- `/api/topics/{id}/content`, `/api/topics/{id}/practice`, `/api/topics/{id}/mastery-quiz` + +### 3.5 `encoach_adaptive_ai` + +**Purpose:** AI logic for diagnostics, plan generation, content generation, coaching. + +**Key services:** +- Diagnostic question selection (adaptive difficulty algorithm) +- Learning plan generation (topological sort of topics by prerequisites, weighted by proficiency gaps) +- AI content generation for topics (call OpenAI GPT-4o with topic context) +- Practice question generation per topic +- Mastery quiz generation and grading +- Coaching: chat, hints, explanations, study suggestions, writing help, contextual tips + +### 3.6 `encoach_lms_api` + +**Purpose:** REST API bridge exposing OpenEduCat models to the frontend. + +**Controller files to create:** +- `courses.py` -- `/api/courses`, `/api/courses/{id}`, `/api/courses/ai-generate` +- `batches.py` -- `/api/batches`, `/api/batches/{id}` +- `timetable.py` -- `/api/timetable` +- `attendance.py` -- `/api/attendance` +- `grades.py` -- `/api/grades` + +**Note:** These controllers wrap the existing OpenEduCat Odoo models (`op.course`, `op.batch`, `op.session`, `op.attendance.sheet`, etc.) behind a clean REST API. Do NOT duplicate the models -- use the OpenEduCat models directly. + +### 3.7 `encoach_sis` + +**Purpose:** UTAS Student Information System integration. + +**Models:** +- `encoach.sis.sync` -- sync job tracking (status, last_run, next_run, error_log) +- `encoach.sis.mapping` -- field mapping between SIS fields and EnCoach fields + +### 3.8 `encoach_branding` + +**Purpose:** Tenant whitelabeling. + +**Models:** +- `encoach.branding` -- `entity_id` (M2O encoach.entity), `logo` (Binary), `primary_color` (Char), `secondary_color` (Char), `font_family` (Char) + +--- + +## 4. Existing Module Modifications + +### 4.1 `encoach_exam` -- Add Subject/Topic Fields + +```python +# In encoach_exam/models/exam.py, add: +subject_id = fields.Many2one('encoach.subject', string='Subject') +topic_ids = fields.Many2many('encoach.topic', string='Topics') +is_diagnostic = fields.Boolean(string='Is Diagnostic Exam', default=False) +``` + +### 4.2 `encoach_stats` -- Add Subject/Topic Fields + +```python +# In encoach_stats/models/stat.py, add: +subject_id = fields.Many2one('encoach.subject', string='Subject') +topic_id = fields.Many2one('encoach.topic', string='Topic') +``` + +### 4.3 `encoach_training` -- Add Subject Field + +```python +# In encoach_training/models/training_tip.py, add: +subject_id = fields.Many2one('encoach.subject', string='Subject') +``` + +### 4.4 `encoach_subscription` -- Add Subject Scoping + +```python +# In encoach_subscription/models/package.py, add: +subject_ids = fields.Many2many('encoach.subject', string='Included Subjects') +``` + +### 4.5 `encoach_ai_generation` -- Multi-Subject Prompts + +Extend the generation prompts to handle: +- **Math:** Include LaTeX formatting instructions in prompts, numerical-tolerance answer checking +- **IT:** Include code block formatting, syntax highlighting hints, code-completion question types +- **Generic:** Accept `subject_id` and `topic_id` parameters, adjust prompt templates per subject + +### 4.6 `encoach_ai_grading` -- Multi-Subject Grading + +Extend grading logic: +- **IELTS:** Band scoring (existing) +- **Math:** Numerical tolerance (accept answers within +/- epsilon), step-by-step partial credit +- **IT:** Keyword matching, regex pattern matching, AI-graded code explanations + +--- + +## 5. Complete API Contract + +This is the definitive list of every endpoint the frontend calls. Each entry shows the HTTP method, path, request body (if POST/PATCH), and expected response shape. **The TypeScript type files in `encoach_frontend_new/src/types/` are the canonical response shapes.** + +### 5.1 Authentication (Existing -- `auth.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/login` | `{ login: string, password: string }` | `{ token: string, user: User }` | +| `POST` | `/api/logout` | -- | `void` | +| `GET` | `/api/user` | -- | `User` | +| `POST` | `/api/reset/sendVerification` | `{ email: string }` | `{ success: boolean }` | + +**User shape:** See `src/types/auth.ts` -- must include: `id`, `name`, `email`, `login`, `user_type` (one of: student/teacher/admin/corporate/mastercorporate/agent/developer), `is_verified`, `entities` (array of {id, name, role}), `classrooms`. + +### 5.2 Users (Existing -- `users.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `GET` | `/api/users/list` | Query: `type`, `entity_id`, `page`, `size`, `search`, `sort`, `order` | `PaginatedResponse` | +| `GET` | `/api/users/{id}` | -- | `User` | +| `PATCH` | `/api/users/update` | `{ id, ...fields }` | `User` | +| `POST` | `/api/users/create` | `Partial ` | `User` | +| `POST` | `/api/batch_users` | `{ users: Partial [] }` | `{ success: boolean }` | + +**PaginatedResponse shape:** `{ items: T[], total: number, page: number, size: number, pages: number }` + +### 5.3 Entities (Existing -- `entities.service.ts`) + +| Method | Path | Response | +|--------|------|----------| +| `GET` | `/api/entities` | `PaginatedResponse ` | +| `GET` | `/api/entities/{id}` | `Entity` | +| `POST` | `/api/entities` | `Entity` | +| `PATCH` | `/api/entities/{id}` | `Entity` | +| `DELETE` | `/api/entities/{id}` | `{ success: boolean }` | +| `GET` | `/api/entities/{id}/roles` | `EntityRole[]` | +| `POST` | `/api/entities/{id}/roles` | `EntityRole` | +| `PATCH` | `/api/roles/{id}/permissions` | `EntityRole` | +| `GET` | `/api/permissions?entity_id=` | `string[]` (list of permission keys for current user) | + +### 5.4 Exams (Existing -- `exams.service.ts`) + +| Method | Path | Response | +|--------|------|----------| +| `GET` | `/api/exam/{module}` | `PaginatedResponse ` | +| `GET` | `/api/exam/{module}/{id}` | `Exam` | +| `POST` | `/api/exam` | `Exam` | +| `PATCH` | `/api/exam/{id}` | `Exam` | +| `DELETE` | `/api/exam/{id}` | `{ success: boolean }` | +| `PATCH` | `/api/exam/{id}/access` | `Exam` | +| `GET` | `/api/rubrics` | `PaginatedResponse ` | +| `POST` | `/api/rubrics` | `Rubric` | +| `GET` | `/api/rubric-groups` | `PaginatedResponse ` | +| `GET` | `/api/exam-structures` | `PaginatedResponse ` | +| `POST` | `/api/exam-structures` | `ExamStructure` | +| `DELETE` | `/api/exam-structures/{id}` | `{ success: boolean }` | +| `GET` | `/api/exam/avatars` | `[{ id, name, thumbnail, voice }]` | + +**Note:** `Exam` response must now include optional `subject_id` and `topic_ids` fields. + +### 5.5 Assignments (Existing -- `assignments.service.ts`) + +| Method | Path | Response | +|--------|------|----------| +| `GET` | `/api/assignments` | `PaginatedResponse ` | +| `GET` | `/api/assignments/{id}` | `Assignment` | +| `POST` | `/api/assignments` | `Assignment` | +| `PATCH` | `/api/assignments/{id}` | `Assignment` | +| `DELETE` | `/api/assignments/{id}` | `{ success: boolean }` | +| `POST` | `/api/assignments/{id}/archive` | `Assignment` | +| `POST` | `/api/assignments/{id}/start` | `Assignment` | + +### 5.6 Classrooms (Existing -- `classrooms.service.ts`) + +| Method | Path | Response | +|--------|------|----------| +| `GET` | `/api/groups` | `PaginatedResponse ` | +| `GET` | `/api/groups/{id}` | `Classroom` | +| `POST` | `/api/groups` | `Classroom` | +| `DELETE` | `/api/groups/{id}` | `{ success: boolean }` | +| `POST` | `/api/groups/transfer` | `{ success: boolean }` | +| `POST` | `/api/groups/{id}/members` | `{ success: boolean }` | +| `POST` | `/api/groups/{id}/members/remove` | `{ success: boolean }` | + +### 5.7 Stats (Existing -- `stats.service.ts`) + +| Method | Path | Response | +|--------|------|----------| +| `GET` | `/api/sessions` | `ExamSession[]` | +| `GET` | `/api/stats` | `ExamStat[]` | +| `GET` | `/api/statistical` | `StatisticalData` | +| `GET` | `/api/stats/performance` | `unknown[]` | + +### 5.8 Evaluations (Existing -- `evaluations.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/evaluate/writing` | `{ session_id, text, rubric_id? }` | `Evaluation` | +| `POST` | `/api/evaluate/speaking` | `{ session_id, audio_url, rubric_id? }` | `Evaluation` | +| `POST` | `/api/grading/multiple` | `{ session_id, answers: [{exercise_index, answer}] }` | `{ results: [{exercise_index, correct, score}] }` | +| `POST` | `/api/transcribe` | `FormData (audio file)` | `{ text: string }` | + +### 5.9 Generation (Existing -- `generation.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/exam/{module}/generate` | `{ title, label?, entity_id?, subject_id?, topic_id?, difficulty?, count? }` | `{ exam_id, exercises: [] }` | +| `POST` | `/api/exam/{module}/generate/scratch` | Same as above | Same as above | + +**Note:** Must now accept `subject_id` and `topic_id` to scope generation to specific subjects/topics. + +### 5.10 Training, Subscriptions, Tickets, Storage, Approvals (Existing) + +These follow the existing patterns. See `training.service.ts`, `subscriptions.service.ts`, `tickets.service.ts`, `storage.service.ts`, `approvals.service.ts` for exact paths. + +--- + +### 5.11 Taxonomy (NEW -- `taxonomy.service.ts`) + +| Method | Path | Request/Params | Response | +|--------|------|---------------|----------| +| `GET` | `/api/subjects` | -- | `Subject[]` | +| `GET` | `/api/subjects/{id}` | -- | `Subject` | +| `POST` | `/api/subjects` | `Partial ` | `Subject` | +| `PATCH` | `/api/subjects/{id}` | `Partial ` | `Subject` | +| `DELETE` | `/api/subjects/{id}` | -- | `{ success: boolean }` | +| `GET` | `/api/subjects/{id}/taxonomy` | -- | `TaxonomyTree` (nested: subject + domains + topics + objectives) | +| `POST` | `/api/subjects/{id}/taxonomy/import` | `FormData (CSV/JSON)` | `{ success: boolean }` | +| `GET` | `/api/domains` | Query: `subject_id?` | `Domain[]` | +| `POST` | `/api/domains` | `Partial ` | `Domain` | +| `PATCH` | `/api/domains/{id}` | `Partial ` | `Domain` | +| `DELETE` | `/api/domains/{id}` | -- | `{ success: boolean }` | +| `POST` | `/api/domains/{id}/ai-suggest` | -- | `{ suggestions: Partial [] }` | +| `GET` | `/api/topics` | Query: `domain_id?`, `subject_id?` | `Topic[]` | +| `POST` | `/api/topics` | `Partial ` | `Topic` | +| `PATCH` | `/api/topics/{id}` | `Partial ` | `Topic` | +| `DELETE` | `/api/topics/{id}` | -- | `{ success: boolean }` | + +**TaxonomyTree shape:** +```json +{ + "subject": { "id": 1, "name": "IELTS", "code": "IELTS", ... }, + "domains": [ + { + "id": 1, "name": "Writing", "code": "W", "sequence": 1, + "topics": [ + { + "id": 1, "name": "Task 1", "difficulty_level": "medium", + "objectives": [ + { "id": 1, "name": "Describe trends", "bloom_level": "apply" } + ] + } + ] + } + ] +} +``` + +### 5.12 Resources (NEW -- `resources.service.ts`) + +| Method | Path | Request/Params | Response | +|--------|------|---------------|----------| +| `GET` | `/api/resources` | Query: `topic_id?`, `resource_type?`, `review_status?`, `page`, `size`, `search` | `PaginatedResponse ` | +| `POST` | `/api/resources` | `FormData` (file + metadata) | `Resource` | +| `PATCH` | `/api/resources/{id}` | `Partial ` | `Resource` | +| `DELETE` | `/api/resources/{id}` | -- | `{ success: boolean }` | +| `POST` | `/api/resources/{id}/complete` | -- | `ResourceCompletion` | +| `POST` | `/api/resources/{id}/rate` | `{ rating: number }` | `{ success: boolean }` | +| `GET` | `/api/resources/{id}/download` | -- | Binary file (stream) | + +### 5.13 Diagnostic Assessment (NEW -- `adaptive.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/diagnostic/start` | `{ subject_id: number }` | `{ session: DiagnosticSession, first_question: DiagnosticQuestion }` | +| `POST` | `/api/diagnostic/answer` | `{ session_id, question_id, answer }` | `{ next_question?: DiagnosticQuestion, completed: boolean }` | +| `GET` | `/api/diagnostic/{id}/result` | -- | `DiagnosticResult` | + +**DiagnosticQuestion shape:** +```json +{ + "id": "q_uuid", + "topic_id": 5, + "topic_name": "Line Graphs", + "domain_name": "Writing", + "difficulty": "medium", + "question_type": "multiple_choice", + "question_text": "Which of the following...", + "options": ["A", "B", "C", "D"], + "time_limit_seconds": 120 +} +``` + +**Diagnostic algorithm:** +1. On `/start`: Create a diagnostic session. Pull topics from all domains. Start with `starting_difficulty` from subject's `diagnostic_config`. +2. On `/answer`: Grade the answer. If correct, increase difficulty and mastery estimate for that topic. If incorrect, decrease. Select the next question from a different domain/topic. Use Computer Adaptive Testing (CAT) principles. +3. Continue until `total_question_cap` reached or all domains have 2+ data points. +4. On completion: Write proficiency records for all assessed topics. + +### 5.14 Proficiency (NEW -- `adaptive.service.ts`) + +| Method | Path | Params | Response | +|--------|------|--------|----------| +| `GET` | `/api/proficiency` | Query: `subject_id?` | `Proficiency[]` | +| `GET` | `/api/proficiency/summary` | -- | `ProficiencySummary[]` | +| `GET` | `/api/proficiency/class` | Query: `subject_id?` | Class-level aggregation | + +**ProficiencySummary shape:** +```json +{ + "subject_id": 1, + "subject_name": "IELTS", + "overall_mastery": 65.5, + "domain_scores": [{ "domain_id": 1, "domain_name": "Writing", "mastery": 72 }], + "topics_mastered": 8, + "topics_total": 20 +} +``` + +### 5.15 Learning Plan (NEW -- `adaptive.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `GET` | `/api/learning-plan` | Query: `subject_id` | `LearningPlan` | +| `POST` | `/api/learning-plan/generate` | `{ subject_id, target_completion? }` | `LearningPlan` | +| `PATCH` | `/api/learning-plan/{id}` | `Partial ` | `LearningPlan` | +| `POST` | `/api/learning-plan/{id}/pause` | -- | `LearningPlan` | +| `POST` | `/api/learning-plan/{id}/resume` | -- | `LearningPlan` | + +**Plan generation algorithm:** +1. Get student's proficiency records for the subject. +2. Identify topics below mastery threshold. +3. Topologically sort topics by prerequisites. +4. Create plan items in sequence. First item = available, rest = locked. +5. Call GPT-4o to generate `ai_summary` (natural language description of the plan). +6. Set `estimated_hours` from topic definitions. + +### 5.16 Content Delivery (NEW -- `adaptive.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `GET` | `/api/topics/{id}/content` | -- | `TopicContent` | +| `POST` | `/api/topics/{id}/generate-content` | -- | `TopicContent` | +| `POST` | `/api/topics/{id}/practice` | -- | `{ questions: [] }` | +| `POST` | `/api/topics/{id}/practice/grade` | `{ answers: [] }` | `{ results: [], mastery_update: number }` | +| `POST` | `/api/topics/{id}/mastery-quiz` | -- | `{ questions: [] }` | +| `POST` | `/api/topics/{id}/mastery-quiz/submit` | `{ answers: [] }` | `{ passed: boolean, score: number, mastery_update: number }` | + +**TopicContent shape:** +```json +{ + "topic_id": 5, + "topic_name": "Line Graphs", + "resources": [{ "id": 1, "name": "...", "resource_type": "pdf", "url": "..." }], + "ai_content": { + "explanation": "Line graphs show...", + "examples": ["Example 1...", "Example 2..."], + "key_points": ["Point 1", "Point 2"], + "model_used": "gpt-4o" + }, + "has_content_gap": true +} +``` + +**`has_content_gap`** = true when no human-uploaded resources exist for this topic, and AI content was generated to fill the gap. + +### 5.17 AI Coaching (NEW -- `coaching.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/coach/chat` | `{ message, context?: { page?, topic_id?, subject_id? }, history?: AiChatMessage[] }` | `{ message: string, suggestions?: string[] }` | +| `POST` | `/api/coach/hint` | `{ topic_id, question_id }` | `{ hint: string }` | +| `POST` | `/api/coach/explain` | `{ context, scores? }` | `{ explanation: string }` | +| `POST` | `/api/coach/suggest` | `{ subject_id? }` | `{ suggestions: string[], study_plan_tips: string[] }` | +| `POST` | `/api/coach/writing-help` | `{ text, task_type }` | `{ feedback, improved, grammar_notes: string[] }` | +| `GET` | `/api/coach/tip` | Query: `context` | `{ title, content, category }` | + +All coaching endpoints use GPT-4o with appropriate system prompts. + +### 5.18 AI Utilities (NEW -- `analytics.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `POST` | `/api/ai/search` | `{ query }` | `AiSearchResult[]` | +| `POST` | `/api/ai/insights` | `{ data: {} }` | `AiInsight[]` | +| `GET` | `/api/ai/alerts` | -- | `AiAlert[]` | +| `POST` | `/api/ai/report-narrative` | `{ report_type, data: {} }` | `{ narrative: string }` | +| `POST` | `/api/ai/batch-optimize` | `{ batch_id }` | `AiBatchOptimization[]` | +| `POST` | `/api/ai/grade-suggest` | `{ submission_id, text, rubric_id? }` | `AiGradingResult` | + +### 5.19 Analytics (NEW -- `analytics.service.ts`) + +| Method | Path | Params | Response | +|--------|------|--------|----------| +| `GET` | `/api/analytics/student` | Query params | Dashboard data | +| `GET` | `/api/analytics/class` | Query params | Class analytics | +| `GET` | `/api/analytics/subject` | Query params | Subject analytics | +| `GET` | `/api/analytics/content-gaps` | Query: `subject_id?` | `{ gaps: [{ topic_id, topic_name, resource_count }] }` | + +### 5.20 LMS Bridge (NEW -- `lms.service.ts`) + +| Method | Path | Request | Response | +|--------|------|---------|----------| +| `GET` | `/api/courses` | Query: `page`, `size`, `search`, `status` | `PaginatedResponse ` | +| `GET` | `/api/courses/{id}` | -- | `Course` | +| `POST` | `/api/courses` | `CourseCreateRequest` | `Course` | +| `PATCH` | `/api/courses/{id}` | `Partial ` | `Course` | +| `DELETE` | `/api/courses/{id}` | -- | `{ success: boolean }` | +| `POST` | `/api/courses/ai-generate` | `{ title, subject_id?, level? }` | `{ outline: {} }` | +| `GET` | `/api/batches` | Query: pagination | `PaginatedResponse ` | +| `GET` | `/api/batches/{id}` | -- | `Batch` | +| `POST` | `/api/batches` | `Partial ` | `Batch` | +| `PATCH` | `/api/batches/{id}` | `Partial ` | `Batch` | +| `DELETE` | `/api/batches/{id}` | -- | `{ success: boolean }` | +| `GET` | `/api/timetable` | Query: `course_id?`, `teacher_id?`, `batch_id?` | `TimetableSession[]` | +| `POST` | `/api/timetable` | `Partial ` | `TimetableSession` | +| `GET` | `/api/attendance` | Query: `course_id?`, `student_id?`, `date?` | `AttendanceRecord[]` | +| `POST` | `/api/attendance` | `{ course_id, date, records: [{student_id, status}] }` | `{ success: boolean }` | +| `GET` | `/api/grades` | Query: `course_id?`, `student_id?` | `GradeRecord[]` | + +**Course shape:** See `src/types/lms.ts` -- must include: `id`, `title`, `code`, `subject_id?`, `subject_name?`, `instructor_id`, `instructor_name`, `description`, `level`, `modules[]`, `enrolled`, `max_capacity`, `status`, `start_date`, `end_date`, `progress?`. + +--- + +## 6. Data Model Specification + +All data model details are in **Section 30 of `ENCOACH_UNIFIED_SRS.md`** (existing models: 30.1, new models: 30.2-30.4). Refer to that document for the complete field-level specification. + +**Key relationships:** +``` +Subject (1) → (N) Domain (1) → (N) Topic (1) → (N) LearningObjective +Topic (M) ↔ (M) Topic (prerequisites -- self-referencing M2M) +Topic (M) ↔ (M) Resource +Student (1) → (N) Proficiency (per topic) +Student (1) → (N) LearningPlan (per subject) → (N) LearningPlanItem (per topic) +Exam → Subject (optional M2O) +Exam → Topic[] (optional M2M) +``` + +--- + +## 7. AI Service Integration + +The following external AI services are already configured (API keys in `encoach_core/data/constants.xml` and server `.env`): + +| Service | Purpose | Config Key | +|---------|---------|-----------| +| OpenAI GPT-4o | Content generation, grading, coaching, plan generation | `encoach.openai_api_key` | +| OpenAI GPT-3.5-turbo | Lightweight tasks (tips, search, suggestions) | Same key | +| OpenAI Whisper (local) | Speech-to-text | Local model | +| AWS Polly (Neural) | Text-to-speech for listening exams | `encoach.aws_access_key_id`, `encoach.aws_secret_access_key` | +| ELAI | AI avatar video generation | `encoach.elai_api_key` | +| GPTZero | AI writing detection | `encoach.gptzero_api_key` | +| FAISS + SentenceTransformers | Semantic similarity for training tips | Local model (`all-MiniLM-L6-v2`) | + +**New AI tasks to implement:** + +| Task | Model | Endpoint | +|------|-------|----------| +| Diagnostic question generation | GPT-4o | `/api/diagnostic/start`, `/answer` | +| Learning plan generation | GPT-4o | `/api/learning-plan/generate` | +| Topic content generation | GPT-4o | `/api/topics/{id}/generate-content` | +| Practice question generation | GPT-4o | `/api/topics/{id}/practice` | +| Mastery quiz generation | GPT-4o | `/api/topics/{id}/mastery-quiz` | +| AI coaching chat | GPT-4o | `/api/coach/chat` | +| Study suggestions | GPT-3.5-turbo | `/api/coach/suggest` | +| Writing feedback | GPT-4o | `/api/coach/writing-help` | +| Grade explanation | GPT-4o | `/api/coach/explain` | +| Contextual tips | GPT-3.5-turbo | `/api/coach/tip` | +| Semantic search | FAISS | `/api/ai/search` | +| Report narrative | GPT-4o | `/api/ai/report-narrative` | +| Data insights | GPT-4o | `/api/ai/insights` | +| Topic suggestion | GPT-4o | `/api/domains/{id}/ai-suggest` | +| Course outline generation | GPT-4o | `/api/courses/ai-generate` | +| Batch optimization | GPT-3.5-turbo | `/api/ai/batch-optimize` | + +--- + +## 8. Response Format Standards + +### 8.1 Paginated Responses + +All list endpoints that support pagination must return: + +```json +{ + "items": [...], + "total": 150, + "page": 1, + "size": 20, + "pages": 8 +} +``` + +Query parameters: `page` (1-based), `size` (default 20), `search` (text filter), `sort` (field name), `order` (asc/desc). + +### 8.2 Error Responses + +```json +{ + "error": "Human-readable error message", + "code": "ERROR_CODE", + "details": {} +} +``` + +HTTP status codes: 400 (validation), 401 (unauthorized), 403 (forbidden), 404 (not found), 500 (server error). + +### 8.3 Success Responses + +For write operations that don't return an entity: + +```json +{ + "success": true, + "message": "Optional message" +} +``` + +### 8.4 JWT Token + +- Token returned on login as `{ token: "...", user: {...} }` +- Frontend sends `Authorization: Bearer ` on every request +- 401 response = token expired, frontend redirects to login + +--- + +## 9. Non-Functional Requirements + +| Requirement | Specification | +|-------------|--------------| +| API response time (CRUD) | < 2 seconds | +| Diagnostic question generation | < 3 seconds | +| AI grading (writing/speaking) | < 60 seconds | +| Learning plan generation | < 10 seconds | +| AI content generation | < 15 seconds | +| Concurrent students per subject | 200 | +| Topics per subject | 500 | +| Resources per subject | 1,000 | +| Pagination on all list endpoints | Required | +| Server-side search/filter | Required | +| Soft delete for resources | Required (preserve completion records) | +| Proficiency weighted averages | Required (never direct overwrite) | +| Math content | LaTeX/KaTeX formatting in question text | +| IT content | Code blocks with language hints in question text | + +--- + +## 10. Implementation Priority + +| Phase | Modules | Endpoints | Estimated Effort | +|-------|---------|-----------|-----------------| +| **P0** | `encoach_taxonomy`, modify `encoach_exam`/`encoach_stats`/`encoach_training` | Taxonomy CRUD (14), Subject/Topic fields on existing models | 1 week | +| **P1** | `encoach_adaptive`, `encoach_adaptive_api`, `encoach_adaptive_ai` | Diagnostic (3), Proficiency (3), Learning Plan (5), Content (6) | 2 weeks | +| **P1** | `encoach_resources` | Resource CRUD (7) | 3 days | +| **P1** | `encoach_lms_api` | LMS bridge (13) | 1 week | +| **P2** | `encoach_adaptive_ai` (coaching) | AI Coaching (6), AI Utilities (5) | 1 week | +| **P2** | Analytics controllers | Analytics (4) | 3 days | +| **P3** | `encoach_sis`, `encoach_branding` | SIS (4), Branding | 1 week | +| **P3** | Multi-subject prompts/grading | Modify generation & grading modules | 3 days | +| **TOTAL** | 8 new modules + 7 modified | ~65 new endpoints | ~7 weeks | + +--- + +## Appendix: File Handoff Checklist + +| File/Folder | Purpose | Location | +|-------------|---------|----------| +| This document | Backend SRS | `ODOO_BACKEND_SRS_v3.md` | +| Product SRS | Full platform specification | `ENCOACH_UNIFIED_SRS.md` | +| TypeScript types | Response shape contracts | `encoach_frontend_new/src/types/*.ts` | +| Service layer | Endpoint path contracts | `encoach_frontend_new/src/services/*.ts` | +| Existing modules | Already-built backend | `ielts-be-v0/` | +| Developer feedback | Review of existing work | `ielts-be-v0/DEVELOPER_FEEDBACK.md` | +| AI keys & config | Service credentials | `ielts-be-v0/encoach_core/data/constants.xml` | + +--- + +*This document supersedes `ODOO_MIGRATION_SRS_v2.md`. The Odoo developer should treat this as the definitive backend specification. All endpoint paths, response shapes, and data models are aligned with the production frontend at `encoach_frontend_new/`.* diff --git a/docs/ODOO_MIGRATION_SRS_v2.md b/docs/ODOO_MIGRATION_SRS_v2.md new file mode 100644 index 0000000..ade8095 --- /dev/null +++ b/docs/ODOO_MIGRATION_SRS_v2.md @@ -0,0 +1,1292 @@ +# EnCoach Platform -- Odoo 19 Full Migration SRS (v2) + +## Software Requirements Specification + +**Version:** 2.0 +**Date:** March 11, 2026 +**Status:** Draft +**Supersedes:** ODOO_MIGRATION_SRS.md (v1) +**Key change from v1:** The ielts-be (FastAPI) microservice is fully eliminated. All AI/ML functionality is absorbed into custom Odoo modules. + +--- + +## Table of Contents + +1. [Executive Summary](#1-executive-summary) +2. [Odoo Module Plan](#2-odoo-module-plan) +3. [Data Models](#3-data-models-odoo-models) +4. [REST API Specification](#4-rest-api-specification) +5. [Authentication & Authorization](#5-authentication--authorization) +6. [AI/ML Services Integration](#6-aiml-services-integration) +7. [Payment Integration](#7-payment-integration) +8. [Business Rules & Workflows](#8-business-rules--workflows) +9. [Data Migration Plan](#9-data-migration-plan) +10. [Non-Functional Requirements](#10-non-functional-requirements) + +--- + +## 1. Executive Summary + +### 1.1 Platform Overview + +EnCoach is an IELTS preparation and English proficiency testing platform serving students, teachers, corporate clients, and administrators. The platform provides: + +- Full IELTS exam simulation (Reading, Listening, Writing, Speaking, Level) +- AI-powered grading of writing and speaking responses (OpenAI GPT-4o) +- AI-powered exam content generation (OpenAI GPT-4o) +- AI-generated audio for listening exams (AWS Polly) +- AI-generated video for speaking exams (ELAI) +- Speech-to-text transcription (OpenAI Whisper) +- AI plagiarism/detection for writing (GPTZero) +- Personalized training recommendations (FAISS + sentence-transformers + GPT) +- Multi-tenant entity (organization) management +- Classroom and assignment management +- Subscription-based access with multiple payment providers +- Support ticket system + +### 1.2 Migration Scope + +**Everything moves to Odoo 19.** Both backend systems are replaced: + +1. **ielts-ui API routes** (Node.js, MongoDB, iron-session, Firebase Auth) -- 108 API route files, 22 MongoDB collections +2. **ielts-be** (Python, FastAPI, OpenAI, Whisper, AWS Polly, ELAI, GPTZero, FAISS) -- all AI/ML workloads + +**What stays unchanged:** + +- ielts-ui browser/frontend code (React components, Zustand stores, pages) +- Strapi CMS (encoachcms) +- Landing page (encoach-landing-page) + +### 1.3 Target Architecture + +``` +ielts-ui (Next.js 14) + └── Browser UI (unchanged) + │ + ▼ +Odoo 19 (Python + PostgreSQL) + ├── REST API Controllers (JSON) + ├── Business Logic (custom modules) + ├── AI/ML Modules + │ ├── OpenAI GPT-4o (grading + content generation) + │ ├── OpenAI Whisper (speech-to-text, local model) + │ ├── AWS Polly (text-to-speech for listening) + │ ├── ELAI (AI video for speaking) + │ ├── GPTZero (AI detection for writing) + │ └── FAISS + sentence-transformers (training search) + ├── PostgreSQL (all data) + ├── Odoo Attachments (file storage) + └── Payment Providers (Stripe, PayPal, Paymob) +``` + +There is no separate microservice. Odoo is the sole backend. + +--- + +## 2. Odoo Module Plan + +### 2.1 Standard Odoo Modules to Leverage + +| Odoo Module | Usage | +|-------------|-------| +| `base` / `res.users` / `res.partner` | User accounts, extended with EnCoach-specific fields | +| `payment` | Payment provider framework for Stripe, PayPal, Paymob | +| `product` | Subscription packages as products | +| `mail` | Email notifications (password reset, verification, invites) | +| `queue_job` (OCA) or `ir.cron` | Background task processing for async AI grading | + +### 2.2 Custom Modules to Develop + +| Module | Depends On | Complexity | Description | +|--------|------------|------------|-------------| +| `encoach_core` | `base`, `mail` | Medium | User type extensions, entity management, roles, permissions, codes, invites | +| `encoach_exam` | `encoach_core` | High | Exam models for 5 modules with complex nested exercise structures | +| `encoach_classroom` | `encoach_core` | Low | Group/classroom management with participant tracking | +| `encoach_assignment` | `encoach_core`, `encoach_exam` | Medium | Assignment lifecycle (create, start, release, archive) | +| `encoach_stats` | `encoach_core`, `encoach_exam` | Medium | Exam sessions, per-exercise statistics, score tracking | +| `encoach_evaluation` | `encoach_core`, `encoach_exam`, `encoach_ai` | Medium | Async grading records for writing/speaking | +| `encoach_training` | `encoach_core`, `encoach_ai` | Medium | Training content, walkthrough, FAISS semantic search | +| `encoach_subscription` | `encoach_core`, `product`, `payment` | Medium | Packages, discounts, subscription management, Stripe/PayPal/Paymob | +| `encoach_registration` | `encoach_core` | Medium | Multi-path registration (individual, corporate), codes, invites | +| `encoach_ticket` | `encoach_core` | Low | Support tickets | +| `encoach_ai` | `base` | **Very High** | Core AI services: OpenAI client, Whisper, AWS Polly, ELAI, GPTZero, FAISS | +| `encoach_ai_grading` | `encoach_ai`, `encoach_exam` | High | Writing/speaking grading logic, rubrics, prompt templates | +| `encoach_ai_generation` | `encoach_ai`, `encoach_exam` | High | Exam content generation for all 5 modules | +| `encoach_ai_media` | `encoach_ai` | High | Audio (Polly TTS), video (ELAI), transcription (Whisper) | +| `encoach_api` | All above | High | All REST JSON controllers for frontend consumption (~60 endpoints) | + +### 2.3 Module Dependency Graph + +``` +encoach_api + ├── encoach_core + │ ├── base / res.users / res.partner + │ └── mail + ├── encoach_exam + │ └── encoach_core + ├── encoach_classroom + │ └── encoach_core + ├── encoach_assignment + │ ├── encoach_core + │ └── encoach_exam + ├── encoach_stats + │ ├── encoach_core + │ └── encoach_exam + ├── encoach_evaluation + │ ├── encoach_core + │ ├── encoach_exam + │ └── encoach_ai + ├── encoach_training + │ ├── encoach_core + │ └── encoach_ai + ├── encoach_subscription + │ ├── encoach_core + │ ├── product + │ └── payment + ├── encoach_registration + │ └── encoach_core + ├── encoach_ticket + │ └── encoach_core + ├── encoach_ai (core AI services) + │ └── base + ├── encoach_ai_grading + │ ├── encoach_ai + │ └── encoach_exam + ├── encoach_ai_generation + │ ├── encoach_ai + │ └── encoach_exam + └── encoach_ai_media + └── encoach_ai +``` + +--- + +## 3. Data Models (Odoo Models) + +All models from v1 remain unchanged. This section adds the AI/ML-related models that were previously managed by ielts-be. + +> **Note:** For the full specification of the following models, refer to Section 3 of the v1 document (`ODOO_MIGRATION_SRS.md`). They are identical: +> `encoach.user`, `encoach.user.entity.rel`, `encoach.entity`, `encoach.role`, `encoach.group`, `encoach.exam`, `encoach.assignment`, `encoach.session`, `encoach.stat`, `encoach.evaluation`, `encoach.package`, `encoach.payment`, `encoach.subscription.payment`, `encoach.ticket`, `encoach.code`, `encoach.invite`, `encoach.permission`, `encoach.discount`, `encoach.walkthrough`, `encoach.approval.workflow` + +The following models are new or significantly expanded in v2: + +### 3.1 `encoach.training` (expanded) + +**Source:** MongoDB `training` collection (previously managed by ielts-be) + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `user_id` | Many2one(`res.users`) | Yes | Student | +| `created_at` | Datetime | Yes | Training generation date | +| `exams` | Text (JSON) | No | JSON array of exam performance summaries: `[{ "id", "date", "performance_comment", "detailed_summary" }]` | +| `tips` | Text (JSON) | No | JSON object with categorized tips from FAISS search | +| `weak_areas` | Text (JSON) | No | JSON array: `[{ "area": "...", "comment": "..." }]` | +| `legacy_id` | Char | No | Original MongoDB ID | + +### 3.2 `encoach.training.tip` (new) + +**Source:** ielts-be `pathways_2_rw_with_ids.json` tips data + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `tip_id` | Char | Yes | Original tip identifier | +| `category` | Selection | Yes | `ct_focus`, `language_for_writing`, `reading_skill`, `strategy`, `writing_skill`, `word_link`, `word_partners` | +| `content` | Text | Yes | Tip text content | +| `embedding` | Binary | No | Pre-computed embedding vector (float32 array) | + +### 3.3 `encoach.ai.job` (new) + +Tracks async AI jobs (grading, video generation). + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `job_type` | Selection | Yes | `writing_grading`, `speaking_grading`, `video_generation` | +| `status` | Selection | Yes | `pending`, `in_progress`, `completed`, `error`. Default: `pending` | +| `user_id` | Many2one(`res.users`) | No | Requesting user | +| `evaluation_id` | Many2one(`encoach.evaluation`) | No | Related evaluation record | +| `input_data` | Text (JSON) | No | Serialized input for the AI task | +| `result_data` | Text (JSON) | No | Serialized result from the AI task | +| `error_message` | Text | No | Error details if failed | +| `created_at` | Datetime | Yes | Job creation time | +| `completed_at` | Datetime | No | Job completion time | +| `retry_count` | Integer | No | Number of retries. Default: 0 | + +### 3.4 `encoach.elai.avatar` (new) + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | Char | Yes | Avatar display name | +| `avatar_code` | Char | Yes | ELAI avatar code | +| `avatar_url` | Char | No | Avatar preview image URL | +| `gender` | Selection | No | `male`, `female` | +| `canvas` | Char | No | ELAI canvas ID | +| `voice_id` | Char | No | ELAI voice ID | +| `voice_provider` | Char | No | Voice provider name | + +### 3.5 Exam `parts` JSON Structure + +Identical to v1. See v1 document Section 3.6.1 for the full JSON schema for Reading, Listening, Writing, Speaking, and Level exam parts. + +### 3.6 Evaluation `result` JSON Structure + +Identical to v1. See v1 document Section 3.10.1 for the writing and speaking grading result schemas. + +--- + +## 4. REST API Specification + +All endpoints from v1 remain unchanged in path and request/response format. The key difference is that endpoints previously marked as "proxy to ielts-be" are now handled directly by Odoo. + +> **Note:** For the full specification of all non-AI endpoints, refer to Section 4 of the v1 document. They are identical: Auth (4.1), Users (4.2), Registration/Codes (4.3), Entities (4.12), Groups (4.11), Roles (4.13), Permissions (4.14), Invites (4.7), Codes (4.8), Assignments (4.10), Sessions (4.11), Stats (4.12), Payments (4.15), Packages (4.16), Discounts (4.17), Tickets (4.18), Storage (4.19), Approval Workflows (4.21). + +The following endpoints were previously "proxy to ielts-be" and are now handled directly by Odoo: + +### 4.1 Exam Generation Endpoints + +#### `GET /api/exam/reading/{passage}` + +Generate a reading passage using AI. + +**Path params:** `passage` = `1` | `2` | `3` + +**Query params:** `topic` (optional), `word_count` (optional, default ~500) + +**Response (200):** +```json +{ + "title": "The Impact of Urbanization", + "text": "Full passage text..." +} +``` + +**Implementation:** Call OpenAI GPT-4o with passage-specific prompt (see Section 6.3.1). + +#### `POST /api/exam/reading/` + +Generate reading exercises from a passage. + +**Request:** +```json +{ + "text": "passage text", + "exercises": [ + { "type": "multipleChoice", "quantity": 5 }, + { "type": "trueFalse", "quantity": 4 }, + { "type": "fillBlanks", "quantity": 3, "num_random_words": 1, "max_words": 3 } + ], + "difficulty": ["B1", "B2"] +} +``` + +**Response (200):** `{ "exercises": [...] }` + +#### `GET /api/exam/listening/{section}` + +Generate a listening dialog/monologue. + +**Path params:** `section` = `1` | `2` | `3` | `4` + +**Query params:** `topic` (optional), `difficulty` (optional) + +**Response (200):** +```json +{ + "dialog": { + "conversation": [ + { "name": "Sarah", "gender": "female", "text": "Hello..." }, + { "name": "James", "gender": "male", "text": "Hi..." } + ] + } +} +``` + +Sections 1 and 3 return `conversation` (dialog). Sections 2 and 4 return `monologue`. + +#### `POST /api/exam/listening/media` + +Generate MP3 audio from a dialog/monologue using AWS Polly TTS. + +**Request:** +```json +{ + "conversation": [ + { "name": "Sarah", "gender": "female", "text": "Hello...", "voice": "Ruth" } + ] +} +``` + +Or for monologue: +```json +{ + "monologue": "Full monologue text..." +} +``` + +**Response:** MP3 binary data (`Content-Type: audio/mpeg`) + +**Implementation:** Call AWS Polly with neural engine (see Section 6.4). + +#### `POST /api/exam/listening/transcribe` + +Transcribe audio using Whisper. + +**Request:** Multipart form with `audio` file + +**Response (200):** Dialog object (conversation or monologue text) + +**Implementation:** Run Whisper model locally, then use GPT-4o to clean overlapping segments (see Section 6.5). + +#### `POST /api/exam/listening/instructions` + +Generate MP3 for listening instructions text. + +**Request:** `{ "text": "instructions text" }` + +**Response:** MP3 binary data + +#### `POST /api/exam/listening/` + +Generate listening exercises from a dialog. + +**Request:** +```json +{ + "text": "transcript text", + "exercises": [ + { "type": "multipleChoice", "quantity": 3 }, + { "type": "writeBlanksFill", "quantity": 5 } + ], + "difficulty": ["B1"] +} +``` + +**Response (200):** `{ "exercises": [...] }` + +#### `GET /api/exam/speaking/{task}` + +Generate a speaking task prompt. + +**Path params:** `task` = `1` | `2` | `3` + +**Query params:** `topic`, `first_topic`, `second_topic`, `difficulty` + +**Response (200):** Speaking part content (prompts array for Part 1/3, text block for Part 2) + +#### `POST /api/exam/speaking/media` + +Generate AI avatar video using ELAI. + +**Request:** +```json +{ + "text": "Question text to speak", + "avatar": "avatar_code" +} +``` + +**Response (200):** `{ "status": "STARTED", "result": null }` (async -- poll for completion) + +#### `GET /api/exam/speaking/media/{vid_id}` + +Poll for video generation status. + +**Response (200):** +```json +{ + "status": "COMPLETED", + "result": "https://elai-video-url..." +} +``` + +Possible statuses: `STARTED`, `IN_PROGRESS`, `COMPLETED`, `ERROR` + +#### `GET /api/exam/speaking/avatars` + +List available ELAI avatars. + +**Response (200):** `[ { "name": "...", "avatar_code": "...", "avatar_url": "...", "gender": "..." } ]` + +#### `GET /api/exam/writing/{task}` + +Generate a writing task prompt. + +**Path params:** `task` = `1` | `2` + +**Query params:** `topic`, `difficulty` + +**Response (200):** Writing task prompt text + +#### `POST /api/exam/writing/{task}/attachment` + +Generate an academic writing Task 1 with image attachment. + +**Request:** Multipart form with `file` (chart/image) and optional `difficulty` + +**Response (200):** Writing task prompt with image analysis + +#### `POST /api/exam/level/` + +Generate level test exercises. + +**Request:** +```json +{ + "exercises": [ + { "type": "multipleChoice", "quantity": 10, "difficulty": "B1" }, + { "type": "fillBlanks", "quantity": 5, "text_size": 200, "topic": "education" } + ], + "difficulty": ["A2", "B1", "B2"] +} +``` + +**Response (200):** Generated exercises + +#### `GET /api/exam/level/` + +Get a pre-built level exam. + +#### `GET /api/exam/level/utas` + +Get a UTAS-format level exam. + +#### `POST /api/exam/level/import/` + +Import a level exam from file. + +**Request:** Multipart form with `exercises` and optional `solutions` files + +**Response (200):** Parsed exam object + +#### `POST /api/exam/level/custom/` + +Generate a custom level exam from JSON specification. + +#### `POST /api/exam/reading/import` + +Import a reading exam from Word/Excel file. + +**Request:** Multipart form with `exercises` and optional `solutions` files + +**Response (200):** Parsed exam object + +#### `POST /api/exam/listening/import` + +Import a listening exam from file. + +### 4.2 Grading Endpoints + +#### `POST /api/evaluate/writing` + +Submit a writing answer for AI grading. + +**Request:** +```json +{ + "userId": 1, + "sessionId": 20, + "exerciseId": "uuid", + "question": "Write about...", + "answer": "Student's essay text...", + "task": 1, + "attachment": "optional-image-url" +} +``` + +**Response (200):** `{ "ok": true }` (grading happens asynchronously) + +**Implementation:** Creates `encoach.evaluation` and `encoach.ai.job`, then runs grading in a background thread (see Section 6.2). + +#### `POST /api/evaluate/speaking` + +Submit speaking audio for AI grading. + +**Request:** Multipart form with `userId`, `sessionId`, `exerciseId`, `task`, and audio files (`audio_1`, `audio_2`, etc.) with corresponding `question_N` fields. + +**Response (200):** `{ "ok": true }` (grading happens asynchronously) + +**Implementation:** Whisper transcribes each audio, then GPT-4o grades the transcript (see Section 6.2). + +#### `POST /api/evaluate/interactiveSpeaking` + +Submit interactive speaking (multiple Q&A pairs) for grading. + +**Request:** Same as speaking but with `question_N` and `audio_N` pairs. + +#### `GET /api/evaluate/{sessionId}/{exerciseId}` + +Poll for evaluation result. + +**Response (200):** +```json +{ + "status": "completed", + "result": { "...grading result..." } +} +``` + +#### `POST /api/grading/multiple` + +Grade multiple short-answer exercises. + +**Request:** +```json +{ + "text": "passage text", + "questions": ["Q1", "Q2"], + "answers": ["A1", "A2"] +} +``` + +**Response (200):** `{ "exercises": [{ "id": "...", "correct": true, "correct_answer": "..." }] }` + +**Implementation:** GPT-4o evaluates each answer against the passage. + +#### `POST /api/exam/grade/summary` + +Generate a grading summary for a full exam session. + +**Request:** +```json +{ + "sections": [ + { "code": "reading", "name": "Reading", "grade": 6.5 }, + { "code": "writing", "name": "Writing", "grade": 7.0 } + ] +} +``` + +**Response (200):** +```json +{ + "sections": [ + { + "code": "reading", + "name": "Reading", + "grade": 6.5, + "evaluation": "Detailed evaluation text...", + "suggestions": "Improvement suggestions...", + "bullet_points": ["Focus on skimming", "Practice time management"] + } + ] +} +``` + +### 4.3 Training Endpoints + +#### `POST /api/training` + +Generate personalized training content. + +**Request:** +```json +{ + "userID": 1, + "stats": [{ "exam_id": "...", "date": 1710000000, "performance_comment": "...", "detailed_summary": "..." }] +} +``` + +**Response (200):** `{ "id": "training-record-id" }` + +**Implementation:** Uses FAISS to find relevant tips, GPT to generate personalized recommendations (see Section 6.6). + +#### `POST /api/training/tips` + +Fetch contextual tips. + +**Request:** +```json +{ + "context": "reading passage about climate change", + "question": "What does the author suggest?", + "answer": "student's wrong answer", + "correct_answer": "the correct answer" +} +``` + +**Response (200):** `{ "tips": "Personalized tip text..." }` + +#### `POST /api/transcribe` + +Transcribe audio file. + +**Request:** Multipart form with audio file + +**Response (200):** Transcript text or dialog object + +#### `POST /api/batch_users` + +Bulk import users. + +**Request:** `{ "makerID": "admin-id", "users": [{ ...userDTO }] }` + +**Response (200):** `{ "ok": true }` + +**Implementation:** In v1 this was handled by ielts-be (which imported into Firebase Auth + MongoDB). In v2, Odoo creates users directly in `res.users`. No Firebase import needed. + +--- + +## 5. Authentication & Authorization + +Identical to v1 document Section 5. Summary: + +- **Recommended:** Odoo native auth with JWT tokens for API access +- **7 user roles** mapped to Odoo security groups with `ir.rule` record rules: student, teacher, corporate, admin, developer, agent, mastercorporate + +--- + +## 6. AI/ML Services Integration + +This is the new Section 6, replacing v1's "ielts-be Integration Specification." Odoo calls all external AI services directly. + +### 6.1 External Service Overview + +| Service | Purpose | API Type | Auth | Odoo Module | +|---------|---------|----------|------|-------------| +| **OpenAI GPT-4o** | Grading, content generation, text correction | REST API | API Key (`Authorization: Bearer`) | `encoach_ai` | +| **OpenAI GPT-3.5-turbo** | Secondary tasks (fixed text, perfect answers, summaries) | REST API | Same key | `encoach_ai` | +| **OpenAI Whisper** | Speech-to-text transcription | **Local model** (Python library) | N/A | `encoach_ai_media` | +| **AWS Polly** | Text-to-speech for listening exams | AWS SDK (boto3) | AWS Access Key + Secret | `encoach_ai_media` | +| **ELAI** | AI avatar video generation for speaking | REST API | Bearer token | `encoach_ai_media` | +| **GPTZero** | AI-generated text detection for writing | REST API | API Key (`x-api-key`) | `encoach_ai_grading` | +| **FAISS** | Semantic search over training tips | **Local library** (Python) | N/A | `encoach_training` | +| **sentence-transformers** | Embedding generation for FAISS | **Local model** (Python) | N/A | `encoach_training` | + +### 6.2 Background Task Architecture + +AI grading operations (writing and speaking) take 10-60 seconds. They must run asynchronously. + +**Recommended approach:** Use Python threading within Odoo or the OCA `queue_job` module. + +**Flow:** + +1. Frontend calls `POST /api/evaluate/writing` (or speaking) +2. Odoo controller creates `encoach.evaluation` record with `status = 'pending'` +3. Odoo controller creates `encoach.ai.job` record and launches a background thread +4. Controller returns `200 OK` immediately to the frontend +5. Background thread: + a. Calls OpenAI GPT-4o for grading (and GPTZero for AI detection in parallel) + b. Updates `encoach.evaluation` with `status = 'completed'` and `result` JSON + c. Updates `encoach.ai.job` with `status = 'completed'` +6. Frontend polls `GET /api/evaluate/{sessionId}/{exerciseId}` until status is `completed` + +**Threading pattern (Odoo-compatible):** + +```python +import threading +from odoo import api, SUPERUSER_ID + +def _run_grading_in_background(dbname, evaluation_id, input_data): + with api.Environment.manage(): + registry = odoo.registry(dbname) + with registry.cursor() as cr: + env = api.Environment(cr, SUPERUSER_ID, {}) + service = env['encoach.ai.grading'] + service.execute_grading(evaluation_id, input_data) + +# In the controller: +thread = threading.Thread( + target=_run_grading_in_background, + args=(request.env.cr.dbname, evaluation.id, input_data) +) +thread.start() +``` + +**Alternative:** Use OCA `queue_job` for production-grade async processing with retry, monitoring, and worker management. + +### 6.3 OpenAI Integration + +#### 6.3.1 Client Configuration + +Create a service class `EncoachOpenAIService` in `encoach_ai`: + +| Parameter | Value | +|-----------|-------| +| **Library** | `openai` (Python package) | +| **Client** | `AsyncOpenAI(api_key=api_key)` or synchronous `OpenAI` | +| **API Key** | Odoo system parameter `encoach.openai_api_key` | +| **Response format** | `response_format={"type": "json_object"}` | + +#### 6.3.2 Models Used + +| Task | Model | Temperature | +|------|-------|-------------| +| Writing grading | `gpt-4o` | 0.1 | +| Speaking grading | `gpt-4o` | 0.1 | +| Short answer grading | `gpt-4o` | 0.1 | +| Fixed text / perfect answer | `gpt-3.5-turbo` | 0.1 | +| Grading summary | `gpt-3.5-turbo` | 0.1 | +| Reading passage generation | `gpt-4o` | 0.7 | +| Listening dialog generation | `gpt-4o` | 0.7 | +| Writing task generation | `gpt-4o` | 0.7 | +| Speaking task generation | `gpt-4o` | 0.7 | +| Level exercise generation | `gpt-4o` | 0.7 | +| Training tips | `gpt-4o` | 0.2 | +| Whisper overlap cleanup | `gpt-4o` | 0.1 | + +#### 6.3.3 Writing Grading Prompts + +**System message:** + +`You are a helpful assistant designed to output JSON on this format: {template}` + +Where `{template}` is: + +```json +{ + "comment": "comment about student's response quality", + "overall": 0.0, + "task_response": { + "Task Achievement": { "grade": 0.0, "comment": "..." }, + "Coherence and Cohesion": { "grade": 0.0, "comment": "..." }, + "Lexical Resource": { "grade": 0.0, "comment": "..." }, + "Grammatical Range and Accuracy": { "grade": 0.0, "comment": "..." } + } +} +``` + +**User message (Task 2):** + +`Evaluate the given Writing Task {task} response based on the IELTS grading system, ensuring a strict assessment that penalizes errors. Deduct points for deviations from the task, and assign a score of 0 if the response fails to address the question. Additionally, provide a detailed commentary highlighting both strengths and weaknesses in the response.\nQuestion: "{question}"\nAnswer: "{answer}"` + +**User message (Task 1 General):** + +Same as Task 2 plus: `Refer to the parts of the letter as: "Greeting Opener", "bullet 1", "bullet 2", "bullet 3", "closer (restate the purpose of the letter)", "closing greeting"` + +**User message (Task 1 Academic with image):** + +The image is sent as base64 in the message content using the vision API. + +**Additional parallel tasks for writing grading:** + +| Task | Model | Prompt | +|------|-------|--------| +| Perfect answer | `gpt-3.5-turbo` | `Write a perfect answer for this IELTS writing task: "{question}"` | +| Fixed text | `gpt-3.5-turbo` | `Fix the grammatical and spelling errors in this text, keeping the original meaning: "{answer}"` | +| AI detection | GPTZero API | See Section 6.7 | + +#### 6.3.4 Speaking Grading Prompts + +**System message:** + +`You are a helpful assistant designed to output JSON on this format: {template}` + +Where `{template}` is: + +```json +{ + "comment": "extensive comment about answer quality", + "overall": 0.0, + "task_response": { + "Fluency and Coherence": { "grade": 0.0, "comment": "extensive comment..." }, + "Lexical Resource": { "grade": 0.0, "comment": "..." }, + "Grammatical Range and Accuracy": { "grade": 0.0, "comment": "..." }, + "Pronunciation": { "grade": 0.0, "comment": "..." } + } +} +``` + +**User message:** + +`Evaluate the given Speaking Part {task} response based on the IELTS grading system, ensuring a strict assessment that penalizes errors. Deduct points for deviations from the task, and assign a score of 0 if the response fails to address the question. Additionally, provide detailed commentary highlighting both strengths and weaknesses in the response.` + +Followed by question/answer pairs from transcription. + +**Task-specific instructions:** + +- Task 1: `Address the student as "you". If the answers are not 2 or 3 sentences long, warn the student that they should be.` +- Task 2: `Address the student as "you"` +- Task 3: `Address the student as "you" and pay special attention to coherence between the answers.` + +**Speaking grading flow:** + +1. Whisper transcribes each audio file +2. GPT-4o grades the full transcript against the rubric +3. GPT-3.5-turbo generates fixed text and perfect answer (for Task 2) +4. Results combined into the evaluation record + +#### 6.3.5 Reading Passage Generation Prompts + +**System message:** + +`You are a helpful assistant designed to output JSON on this format: {"title": "title of the text", "text": "generated text"}` + +**User message (Passage 1):** + +`Generate an extensive text for IELTS Reading Passage 1, of at least {word_count} words, on the topic of "{topic}". The passage should offer a substantial amount of information relevant to the chosen subject matter. It should be fairly easy and consist of multiple paragraphs. Make sure that the generated text does not contain forbidden subjects in muslim countries.` + +**User message (Passage 2):** + +Same structure but: `fairly hard and consist of multiple paragraphs` + +**User message (Passage 3):** + +Same structure but: `very hard, present different points or theories, cite different sources, and consist of multiple paragraphs` + +#### 6.3.6 Listening Dialog Generation Prompts + +**Section 1 (conversation, 2 people):** + +System: `{"conversation": [{"name": "name", "gender": "gender", "text": "text"}]}` + +User: `Compose an authentic conversation between two individuals on the topic of "{topic}". Please include random names and genders. Include misleading discourse (dates, colors, etc.) and spelling of names. Make sure that the generated conversation does not contain forbidden subjects in muslim countries.` + +**Section 2 (monologue, social):** + +System: `{"monologue": "monologue"}` + +User: `Generate a comprehensive monologue set in the social context of "{topic}". Make sure that the generated monologue does not contain forbidden subjects in muslim countries.` + +**Section 3 (conversation, up to 4 people):** + +User: `Compose an authentic and elaborate conversation between up to four individuals on the topic of "{topic}". Please include random names and genders. Make sure that the generated conversation does not contain forbidden subjects in muslim countries.` + +**Section 4 (monologue, academic):** + +User: `Generate a comprehensive and complex monologue on the academic subject of "{topic}". Make sure that the generated monologue does not contain forbidden subjects in muslim countries.` + +#### 6.3.7 Writing Task Generation Prompts + +**General Task 1:** + +`Craft a prompt for an IELTS Writing Task 1 General Training exercise that instructs the student to compose a letter based on the topic of "{topic}" of {difficulty} CEFR level difficulty. The prompt should end with "In the letter you should" followed by 3 bullet points. Make sure it does not contain forbidden subjects in muslim countries.` + +**General Task 2:** + +`Craft a comprehensive question of {difficulty} CEFR level difficulty like the ones for IELTS Writing Task 2 General Training that directs the candidate to delve into an in-depth analysis of contrasting perspectives on the topic of "{topic}". The question should lead to an answer with either "theories", "complicated information" or be "very descriptive" on the topic.` + +**Academic Task 1 (with image):** + +`Analyze the uploaded image and create a detailed IELTS Writing Task 1 Academic prompt. Describe the visual type, context, and create a prompt at {difficulty} CEFR level.` + +#### 6.3.8 Speaking Task Generation Prompts + +**Part 1:** + +`Craft 5 simple and single questions of easy difficulty for IELTS Speaking Part 1 that encourages candidates to delve deeply into personal experiences, preferences, or insights on the topic of "{first_topic}" and the topic of "{second_topic}". The questions should lead to the usage of 4 verb tenses (present perfect, present, past and future). Make sure that the generated question does not contain forbidden subjects in muslim countries.` + +**Part 2:** + +`Create a question of medium difficulty for IELTS Speaking Part 2 that encourages candidates to narrate a personal experience or story related to the topic of "{topic}". Include 3 prompts that guide the candidate. The prompts must not be questions. Also include a suffix like the ones in the IELTS exams that start with "And explain why". Make sure that the generated question does not contain forbidden subjects in muslim countries.` + +**Part 3:** + +`Formulate a set of 5 single questions of hard difficulty for IELTS Speaking Part 3 that encourage candidates to engage in a meaningful discussion on the topic of "{topic}". They must be 1 single question each and not be double-barreled questions. Make sure that the generated question does not contain forbidden subjects in muslim countries.` + +#### 6.3.9 Level Exercise Generation Prompts + +**Multiple Choice:** + +`Generate {quantity} multiple choice questions of 4 options for an english level exam of {difficulty} CEFR level. Ensure that the questions cover a range of topics such as verb tense, subject-verb agreement, pronoun usage, sentence structure, and punctuation. Make sure every question only has 1 correct answer.` + +**Fill Blanks:** + +`Generate a text of at least {size} words about the topic {topic}. From the generated text choose exactly {quantity} words (cannot be sequential words), replace each with {{id}}, and generate a JSON object containing: the modified text, solutions array, words array with four options per blank.` + +#### 6.3.10 Retry and Validation Logic + +- Retry up to 2 times if response contains blacklisted words or missing required fields +- Blacklisted words include topics related to religion, politics, explicit content, and culturally sensitive subjects (stored in constants) +- Token limit: `4097 - input_token_count - 300` for max_tokens +- All responses expected in JSON format via `response_format={"type": "json_object"}` + +### 6.4 AWS Polly Integration (TTS) + +Implemented in `encoach_ai_media` module. + +| Parameter | Value | +|-----------|-------| +| **Library** | `boto3` / `aioboto3` | +| **Service** | `polly` | +| **Engine** | `neural` | +| **Output format** | `mp3` | +| **Auth** | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` (Odoo system parameters) | + +**Available voices:** + +| Voice | Gender | Accent | +|-------|--------|--------| +| Danielle | Female | US | +| Gregory | Male | US | +| Kevin | Male | US | +| Ruth | Female | US | +| Stephen | Male | US | +| Arthur | Male | GB | +| Olivia | Female | GB | +| Ayanda | Female | ZA | +| Aria | Female | NZ | +| Kajal | Female | IN | +| Niamh | Female | IE | + +**Conversation audio generation:** + +1. For each dialog line, call Polly `synthesize_speech()` with the assigned voice +2. Chunk text at sentence boundaries if > 3000 characters +3. Concatenate all MP3 segments +4. Append final message: `"This audio recording, for the listening exercise, has finished."` (voice: Stephen) + +**Monologue audio generation:** + +1. Select a random voice +2. Chunk text at sentence boundaries if > 3000 characters +3. Synthesize each chunk and concatenate + +### 6.5 Whisper Integration (STT) + +Implemented in `encoach_ai_media` module. + +| Parameter | Value | +|-----------|-------| +| **Library** | `openai-whisper` (local Python package) | +| **Model size** | `base` (~1 GB, or configurable) | +| **Model instances** | 4 (for parallel transcription) | +| **Thread pool** | `ThreadPoolExecutor(max_workers=4)` | +| **Audio resampling** | 16 kHz, normalized | +| **Chunk size** | 30 seconds (480,000 samples) with 1/4 overlap | +| **Options** | `fp16=False`, `language='English'`, `verbose=False` | +| **Retries** | 3 attempts via tenacity | + +**Long audio handling:** + +1. Split audio into 30-second chunks with 25% overlap +2. Transcribe each chunk independently +3. Use GPT-4o to remove duplicated words at chunk boundaries +4. Join cleaned segments into final transcript + +**Audio-to-dialog conversion:** + +After transcription, use GPT-4o to determine whether the transcript is a conversation or monologue and output structured JSON: + +``` +You are a helpful assistant designed to output JSON on either one of these formats: +1 - {"dialog": [{"name": "name", "gender": "gender", "text": "text"}]} +2 - {"dialog": "text"} + +A transcription of an audio file will be provided to you. Based on that transcription you will need to determine whether the transcription is a conversation or a monologue. If it is a conversation, output format 1. If it is a monologue, output format 2. +``` + +### 6.6 ELAI Integration (AI Video) + +Implemented in `encoach_ai_media` module. + +| Parameter | Value | +|-----------|-------| +| **Base URL** | `https://apis.elai.io/api/v1/videos` | +| **Auth** | Bearer token (Odoo system parameter `encoach.elai_token`) | + +**Video generation flow:** + +1. `POST /api/v1/videos` -- Create video with avatar, text, voice +2. `POST /api/v1/videos/render/{video_id}` -- Start rendering +3. Poll `GET /api/v1/videos/{video_id}` -- Check status (`ready`, `failed`, or in progress) +4. Return video URL when `ready` + +**Avatar configuration:** Stored in `encoach.elai.avatar` model (avatar codes, voice IDs, voice providers). + +### 6.7 GPTZero Integration (AI Detection) + +Implemented in `encoach_ai_grading` module. + +| Parameter | Value | +|-----------|-------| +| **Endpoint** | `https://api.gptzero.me/v2/predict/text` | +| **Auth** | `x-api-key` header (Odoo system parameter `encoach.gptzero_api_key`) | + +**Request:** +```json +{ + "document": "student's writing text", + "version": "", + "multilingual": false +} +``` + +**Response fields used:** +- `class_probabilities` -- probability scores for human/AI/mixed +- `predicted_class` -- `human`, `ai`, or `mixed` +- `sentences[].highlight_sentence_for_ai` -- boolean per sentence + +**Result stored in evaluation:** `"ai_detection": { "probability": 0.12, "predicted_class": "human" }` + +### 6.8 FAISS + Sentence-Transformers (Training) + +Implemented in `encoach_training` module. + +| Parameter | Value | +|-----------|-------| +| **Embeddings model** | `sentence-transformers/all-MiniLM-L6-v2` | +| **Index type** | `faiss.IndexFlatL2` (one per category) | +| **Top-K results** | 5 | + +**Categories:** + +| Category | Description | +|----------|-------------| +| `ct_focus` | Critical thinking focus tips | +| `language_for_writing` | Language for writing tips | +| `reading_skill` | Reading skills tips | +| `strategy` | Test strategy tips | +| `writing_skill` | Writing skills tips | +| `word_link` | Word linking tips | +| `word_partners` | Word partner/collocation tips | + +**Index files:** Store as Odoo attachments or on disk: +- `{category}_tips_index.faiss` -- FAISS index file per category +- `tips_metadata.pkl` -- Metadata mapping index positions to tip content + +**Query flow:** + +1. Encode user query using sentence-transformers: `embedding = model.encode([query])` +2. Search FAISS index: `distances, indices = index.search(embedding, top_k=5)` +3. Retrieve tip content from metadata +4. Pass tips + user performance data to GPT for personalized recommendations + +**Training content generation flow:** + +1. Receive user's exam stats (performance comments, detailed summaries) +2. Use GPT to identify weak areas and generate training queries +3. For each query, search FAISS index to find relevant tips +4. Use GPT to generate personalized recommendations based on tips + performance +5. Store result in `encoach.training` + +### 6.9 Topic and Content Constants + +Maintain in `encoach_ai` module as constants or configuration records: + +**Topics for content generation:** + +- `TOPICS` -- General IELTS topics (education, technology, environment, health, etc.) +- `TWO_PEOPLE_SCENARIOS` -- Listening Section 1 scenarios +- `SOCIAL_MONOLOGUE_CONTEXTS` -- Listening Section 2 contexts +- `FOUR_PEOPLE_SCENARIOS` -- Listening Section 3 scenarios +- `ACADEMIC_SUBJECTS` -- Listening Section 4 subjects + +**Difficulty levels:** `["A1", "A2", "B1", "B2", "C1", "C2"]` + +**Blacklisted words:** List of culturally sensitive terms that trigger retry if found in AI-generated content (religion, politics, explicit content, etc.) + +### 6.10 Python Dependencies + +Add to the Odoo server's Python environment: + +| Package | Version | Purpose | +|---------|---------|---------| +| `openai` | >= 1.50 | OpenAI API client (GPT-4o, GPT-3.5) | +| `openai-whisper` | latest | Local Whisper model for STT | +| `boto3` | >= 1.34 | AWS SDK for Polly TTS | +| `faiss-cpu` | >= 1.7 | FAISS vector search | +| `sentence-transformers` | >= 3.0 | Embeddings for FAISS | +| `httpx` | >= 0.27 | HTTP client for ELAI, GPTZero | +| `tiktoken` | >= 0.7 | Token counting for OpenAI | +| `librosa` | >= 0.10 | Audio processing for Whisper | +| `soundfile` | >= 0.12 | Audio file I/O | +| `numpy` | >= 1.26 | Numerical operations | +| `tenacity` | >= 8.2 | Retry logic for API calls | +| `torch` | >= 2.0 | Required by Whisper and sentence-transformers | + +**Server requirements for Whisper:** The Odoo server needs at least 4 GB RAM for the Whisper base model and sentence-transformers model. Consider running Whisper on a GPU for better performance, or use the OpenAI Whisper API instead of local inference. + +### 6.11 Configuration (Odoo System Parameters) + +| Parameter Key | Description | Example | +|--------------|-------------|---------| +| `encoach.openai_api_key` | OpenAI API key | `sk-...` | +| `encoach.aws_access_key_id` | AWS access key | `AKIA...` | +| `encoach.aws_secret_access_key` | AWS secret key | `wJal...` | +| `encoach.elai_token` | ELAI API token | `Bearer ...` | +| `encoach.gptzero_api_key` | GPTZero API key | `...` | +| `encoach.whisper_model_size` | Whisper model size | `base` (or `small`, `medium`, `large`) | +| `encoach.whisper_workers` | Number of Whisper worker threads | `4` | +| `encoach.grading_temperature` | Temperature for grading prompts | `0.1` | +| `encoach.generation_temperature` | Temperature for content generation | `0.7` | +| `encoach.tips_temperature` | Temperature for training tips | `0.2` | + +--- + +## 7. Payment Integration + +Identical to v1 document Section 7. Summary: + +- **Stripe:** Checkout session creation, webhook handling, subscription extension +- **PayPal:** Order creation, capture, subscription update +- **Paymob:** Intention creation, transaction webhook verification +- Subscription logic: extend `subscriptionExpirationDate` on successful payment; propagate to entity members for corporate + +--- + +## 8. Business Rules & Workflows + +Identical to v1 document Section 8, with one addition: + +### 8.1-8.7 (Unchanged from v1) + +See v1 for: Registration flow, Subscription management, Entity licensing, Assignment lifecycle, Grading polling, Corporate payment activation, User change propagation. + +### 8.8 AI Content Moderation (New) + +All AI-generated content must be validated before being returned to the user: + +1. **Blacklist check:** Scan generated text against the blacklisted words list +2. **Retry logic:** If blacklisted words are found, regenerate with the same prompt (up to 2 retries) +3. **JSON validation:** Verify the AI response is valid JSON with all required fields +4. **Score validation:** For grading, verify all scores are between 0.0 and 9.0 +5. **Content length:** For passages, verify minimum word count is met + +### 8.9 Batch User Import (Updated) + +In v1, batch import was proxied to ielts-be (which imported into Firebase Auth). In v2: + +1. Admin uploads CSV/Excel with user data +2. Odoo parses the file and creates `res.users` records directly +3. Generates temporary passwords and sends welcome emails +4. Creates registration codes and group assignments +5. No Firebase Auth involvement + +--- + +## 9. Data Migration Plan + +### 9.1 Migration Strategy + +Perform a one-time data migration from both data sources: + +1. **MongoDB** (used by ielts-ui) -> PostgreSQL +2. **MongoDB** (used by ielts-be) -> PostgreSQL (evaluation records, training data) +3. **Firebase Auth** users -> Odoo `res.users` + +### 9.2 Collection-to-Model Mapping + +Identical to v1 Section 9.2, with additional ielts-be collections: + +| ielts-be MongoDB Collection | Odoo Model | Notes | +|----------------------------|------------|-------| +| `evaluation` | `encoach.evaluation` | Grading records with result JSON | +| `training` | `encoach.training` | Training content | + +### 9.3 Additional Migration: AI Assets + +| Asset | Source | Target | +|-------|--------|--------| +| FAISS index files | ielts-be `./faiss/` directory | Odoo server filesystem or `ir.attachment` | +| Tips metadata | `tips_metadata.pkl` | `encoach.training.tip` model records | +| Tips source data | `pathways_2_rw_with_ids.json` | `encoach.training.tip` model records | +| Avatar config | `conf.json`, `avatars.json` | `encoach.elai.avatar` model records | +| Whisper model | Downloaded at runtime | Odoo server filesystem | + +### 9.4 Migration Script Requirements & Import Order + +Identical to v1 Section 9.3 and 9.4, with the addition of: + +22. `encoach.training.tip` (from tips JSON) +23. `encoach.elai.avatar` (from avatar config JSON) +24. FAISS index rebuild (re-embed tips into FAISS after import) + +--- + +## 10. Non-Functional Requirements + +### 10.1-10.6 (Unchanged from v1) + +See v1 for: API response format, CORS, file storage, performance targets, scalability, security. + +### 10.7 AI/ML Performance Requirements (New) + +| Operation | Target Response Time | Notes | +|-----------|---------------------|-------| +| Reading passage generation | < 15s | Single GPT-4o call | +| Listening dialog generation | < 15s | Single GPT-4o call | +| Listening MP3 generation | < 30s | Multiple Polly calls + concatenation | +| Writing task generation | < 10s | Single GPT-4o call | +| Speaking task generation | < 10s | Single GPT-4o call | +| Level exercise generation | < 20s | Multiple GPT-4o calls | +| Writing grading (async) | < 30s total | Parallel: GPT-4o grading + GPTZero + perfect answer + fixed text | +| Speaking grading (async) | < 60s total | Sequential: Whisper transcription + GPT-4o grading | +| Short answer grading | < 10s | Single GPT-4o call | +| Transcription (1 min audio) | < 15s | Local Whisper | +| FAISS query | < 1s | Local computation | +| Video generation (async) | 1-5 min | ELAI rendering; poll for completion | + +### 10.8 AI/ML Scalability (New) + +- Whisper model requires ~1 GB RAM per instance (4 instances = 4 GB) +- sentence-transformers model requires ~500 MB RAM +- FAISS indices are small (< 100 MB total) +- OpenAI API has rate limits -- implement request queuing if needed +- AWS Polly has a 3000-character limit per request -- chunking is already handled +- Consider GPU acceleration for Whisper if transcription volume is high + +### 10.9 AI/ML Monitoring (New) + +- Log all OpenAI API calls with model, token usage, response time, and cost +- Log all Polly synthesis calls with character count +- Log all ELAI video generation requests with status and duration +- Log all Whisper transcription jobs with audio duration and processing time +- Alert on repeated grading failures (> 3 consecutive errors) +- Track OpenAI API spend against budget thresholds + +### 10.10 Logging, Testing (Unchanged from v1) + +See v1 Sections 10.7 and 10.8. + +--- + +## Appendix A: Exercise Type Reference + +Identical to v1 Appendix A. + +## Appendix B: Grading Rubric Details + +Identical to v1 Appendix B. + +## Appendix C: Environment Variables + +Updated to include all AI/ML service credentials: + +| Variable | Description | Example | +|----------|-------------|---------| +| `ENCOACH_JWT_SECRET` | JWT signing secret | Random 256-bit string | +| `OPENAI_API_KEY` | OpenAI API key | `sk-...` | +| `AWS_ACCESS_KEY_ID` | AWS access key for Polly | `AKIA...` | +| `AWS_SECRET_ACCESS_KEY` | AWS secret key for Polly | `wJal...` | +| `ELAI_TOKEN` | ELAI API bearer token | `...` | +| `GPT_ZERO_API_KEY` | GPTZero API key | `...` | +| `WHISPER_MODEL_SIZE` | Whisper model size | `base` | +| `STRIPE_SECRET_KEY` | Stripe secret key | `sk_live_...` | +| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | `whsec_...` | +| `PAYPAL_CLIENT_ID` | PayPal client ID | `AX...` | +| `PAYPAL_CLIENT_SECRET` | PayPal client secret | `EL...` | +| `PAYPAL_ACCESS_TOKEN_URL` | PayPal OAuth URL | `https://api.paypal.com/v1/oauth2/token` | +| `PAYMOB_API_KEY` | Paymob API key | `ZXlK...` | +| `PAYMOB_SECRET` | Paymob webhook secret | `...` | + +## Appendix D: Glossary + +Identical to v1 Appendix D, with additions: + +| Term | Definition | +|------|-----------| +| **Whisper** | OpenAI's speech-to-text model, run locally on the Odoo server | +| **Polly** | AWS text-to-speech service for generating listening exam audio | +| **ELAI** | AI video generation service for creating speaking exam avatar videos | +| **GPTZero** | AI text detection service for identifying AI-generated writing submissions | +| **FAISS** | Facebook AI Similarity Search -- vector search library for finding relevant training tips | +| **sentence-transformers** | Python library for generating text embeddings used with FAISS | + +## Appendix E: Custom Odoo Module Summary + +| Module | Complexity | Key Models | Key Services | External APIs | +|--------|------------|------------|-------------- |---------------| +| `encoach_core` | Medium | User extensions, Entity, Role, Permission, Code, Invite | Registration, entity management | -- | +| `encoach_exam` | High | Exam (5 modules), Exercise structures | Exam CRUD, import/export | -- | +| `encoach_classroom` | Low | Group | Group management | -- | +| `encoach_assignment` | Medium | Assignment | Assignment lifecycle | -- | +| `encoach_stats` | Medium | Session, Stat | Stats tracking, PDF export | -- | +| `encoach_evaluation` | Medium | Evaluation, AI Job | Async grading coordination | -- | +| `encoach_training` | Medium | Training, Training Tip | FAISS search, tip generation | -- | +| `encoach_subscription` | Medium | Package, Payment, Subscription Payment, Discount | Subscription management | Stripe, PayPal, Paymob | +| `encoach_registration` | Medium | Code, Invite | Multi-path registration | -- | +| `encoach_ticket` | Low | Ticket | Ticket CRUD | -- | +| `encoach_ai` | **Very High** | -- | OpenAI client, prompt management, retry logic | OpenAI API | +| `encoach_ai_grading` | High | -- | Writing grading, speaking grading, short answer grading | OpenAI, GPTZero | +| `encoach_ai_generation` | High | -- | Reading/listening/writing/speaking/level generation | OpenAI | +| `encoach_ai_media` | High | ELAI Avatar | Polly TTS, Whisper STT, ELAI video | AWS Polly, ELAI, Whisper (local) | +| `encoach_api` | High | -- | REST JSON controllers (~60 endpoints) | -- | diff --git a/docs/UTAS_MASTER_PLAN.md b/docs/UTAS_MASTER_PLAN.md new file mode 100644 index 0000000..65b0615 --- /dev/null +++ b/docs/UTAS_MASTER_PLAN.md @@ -0,0 +1,727 @@ +# EnCoach -- UTAS University Deployment Master Plan + +**Document Version:** 1.0 +**Date:** March 16, 2026 +**Classification:** Confidential -- EnCoach & UTAS Internal +**Prepared by:** EnCoach Engineering Team + +--- + +## Table of Contents + +1. [Executive Summary](#1-executive-summary) +2. [Project Scope and Objectives](#2-project-scope-and-objectives) +3. [System Architecture](#3-system-architecture) +4. [Feature Modules Breakdown](#4-feature-modules-breakdown) +5. [Development Phases](#5-development-phases) +6. [University Integration Plan](#6-university-integration-plan) +7. [Master Timeline](#7-master-timeline) +8. [Testing and Rollout Strategy](#8-testing-and-rollout-strategy) +9. [Team and Responsibilities](#9-team-and-responsibilities) +10. [Risk Register](#10-risk-register) +11. [Assumptions and Prerequisites](#11-assumptions-and-prerequisites) + +--- + +## 1. Executive Summary + +EnCoach is deploying a comprehensive AI-powered education platform for the University of Technology and Applied Sciences (UTAS). The platform delivers nine integrated modules spanning self-paced English learning, AI-graded examinations, Mathematics and IT courses with AI-assisted assessment, a full Learning Management System, university SIS integration, institutional whitelabeling, AI virtual tutors, and a support ticketing system. + +### Platform at a Glance + +| Dimension | Detail | +|-----------|--------| +| **Modules** | 9 (English Self Learning, Examinations, Math, IT, LMS, Whitelabeling, Humanisation, SIS Integration, Ticketing) | +| **AI Services** | 6 (OpenAI GPT-4o, Whisper STT, AWS Polly TTS, ELAI Avatars, GPTZero Detection, FAISS Semantic Search) | +| **Backend** | Odoo 19 -- 15 custom modules, 70+ REST API endpoints, PostgreSQL | +| **Frontend** | React 18 SPA -- 55+ pages, TypeScript, Tailwind CSS, shadcn/ui | +| **LMS Engine** | OpenEduCat (Odoo-native modules) | +| **Team** | 1 Solution Architect, 1 Senior Developer (AI-assisted), 1 Full-Stack Developer (AI-assisted) | + +### Key Milestones + +| Milestone | Target Date | +|-----------|-------------| +| Project Kickoff | April 1, 2026 | +| Staging Demo to UTAS | April 24, 2026 | +| English Self Learning + Examinations -- GO LIVE | June 12, 2026 | +| Math + IT + LMS + Ticketing -- GO LIVE | July 24, 2026 | +| Full Platform GO LIVE (SIS, Whitelabeling, Humanisation) | August 21, 2026 | + +### Current State of Development + +The platform is not starting from zero. Significant engineering has already been completed: + +- **Backend (Odoo 19):** 15 custom modules fully developed, covering exams, AI grading, content generation, media services, training, assignments, classrooms, subscriptions, registration, and ticketing. All AI services (OpenAI, Whisper, Polly, ELAI, GPTZero, FAISS) are integrated and configured. The backend is deployed and running on a staging server. +- **Frontend (React):** A complete UI prototype with 55+ pages across student, teacher, and admin portals. Role-based routing, layouts, and forms are built. Currently using mock data -- API integration is the primary remaining work. +- **Infrastructure:** Docker Compose deployment with PostgreSQL, staging environment operational at the current staging server. + +--- + +## 2. Project Scope and Objectives + +### 2.1 Objectives + +1. **Deliver a production-ready AI-powered education platform** for UTAS covering English, Mathematics, and IT subjects. +2. **Integrate with UTAS's existing Student Information System (SIS)** for seamless student enrollment, grade sync, and attendance tracking. +3. **Provide institutional whitelabeling** with UTAS branding, colors, and identity. +4. **Deploy AI virtual tutors** (Humanisation) to enhance the student learning experience across subjects. +5. **Establish a scalable LMS** built on OpenEduCat for course management, timetabling, and academic administration. + +### 2.2 Module Scope + +| # | Module | Description | Acceptance Criteria | +|---|--------|-------------|---------------------| +| 1 | **English Self Learning** | AI-powered IELTS preparation: Reading, Listening, Writing, Speaking practice with AI grading, personalized training tips, and progress tracking. | Students can complete full practice sessions in all 4 skills; AI grading returns scores within 60 seconds; training tips are personalized to weaknesses. | +| 2 | **Examinations** | Full exam lifecycle: AI-generated exam content, timed exam sessions, automated grading, results dashboard, and teacher approval workflows. | Teachers can generate, review, and assign exams; students complete timed exams; grading is automated with band scores and feedback. | +| 3 | **Mathematics** | Math courses and AI-assisted exams: course content delivery, formula-based question generation, automated grading for numerical and short-answer responses. | Students access Math courses; AI generates Math questions at configurable difficulty levels; automated grading handles numerical and short-answer formats. | +| 4 | **Information Technology** | IT courses and AI-assisted exams: course content delivery, conceptual and practical question generation, automated grading for MCQ and short-answer formats. | Students access IT courses; AI generates IT questions across topics; automated grading covers MCQ, true/false, and short answers. | +| 5 | **LMS (OpenEduCat)** | Course management, enrollment, timetabling, attendance tracking, batch management, gradebook, and academic reporting via OpenEduCat Odoo modules. | Admin manages courses, batches, and timetables; teachers record attendance and grades; students view schedules and progress. | +| 6 | **Whitelabeling** | UTAS institutional branding: customizable logo, color palette, fonts, login screen, email templates, and report headers. | Platform displays UTAS branding throughout; branding is configurable via admin settings without code changes. | +| 7 | **Humanisation** | AI-powered virtual tutors using ELAI avatar technology: video-based tutoring, interactive speaking practice, and subject-specific AI guidance. | AI avatars present content and interact with students across subjects; avatar selection is available; videos generate within 2 minutes. | +| 8 | **SIS Integration** | Bidirectional integration with UTAS's existing Student Information System: student enrollment sync, grade export, attendance sync, and course catalog mapping. | Student data syncs from SIS to EnCoach on enrollment; grades and attendance export back to SIS; data is consistent within 15-minute sync cycles. | +| 9 | **Ticketing** | Support ticket system for students and staff: ticket creation, categorization, assignment, status tracking, and resolution workflow. | Users submit tickets; staff can categorize, assign, and resolve; status updates are visible to the reporter. | + +### 2.3 Out of Scope (This Engagement) + +- Mobile native applications (iOS/Android) +- Content creation for Math and IT courses (content to be provided by UTAS or generated via AI) +- UTAS internal network infrastructure changes +- Marketing website / landing page redesign +- Payment gateway integration for UTAS (institutional licensing assumed) + +--- + +## 3. System Architecture + +### 3.1 Architecture Overview + +```mermaid +graph TB + subgraph frontend ["Frontend Layer"] + ReactApp["React 18 SPA
TypeScript + Tailwind + shadcn/ui
55+ pages, role-based routing"] + end + + subgraph backend ["Backend Layer -- Odoo 19"] + API["REST API Controllers
70+ JSON endpoints"] + Core["Core Modules
Users, Entities, Roles, Permissions"] + Exam["Exam Engine
Reading, Listening, Writing, Speaking, Level"] + LMS["OpenEduCat LMS
Courses, Batches, Timetable, Attendance"] + AI["AI Modules
Generation, Grading, Media, Training"] + Sub["Business Modules
Subscriptions, Tickets, Registration"] + end + + subgraph aiServices ["AI Services"] + GPT["OpenAI GPT-4o
Content Generation + Grading"] + Whisper["OpenAI Whisper
Speech-to-Text (local)"] + Polly["AWS Polly
Text-to-Speech (neural)"] + ELAI["ELAI
AI Avatar Video"] + GPTZero["GPTZero
AI Writing Detection"] + FAISS["FAISS + SentenceTransformers
Semantic Search"] + end + + subgraph dataLayer ["Data Layer"] + PG["PostgreSQL 16"] + end + + subgraph external ["External Systems"] + SIS["UTAS SIS
Student Information System"] + end + + ReactApp -->|"JWT Auth + REST API"| API + API --> Core + API --> Exam + API --> LMS + API --> AI + API --> Sub + AI --> GPT + AI --> Whisper + AI --> Polly + AI --> ELAI + AI --> GPTZero + AI --> FAISS + Core --> PG + Exam --> PG + LMS --> PG + Sub --> PG + API <-->|"REST API Sync"| SIS +``` + +### 3.2 Technology Stack + +| Layer | Technology | Details | +|-------|-----------|---------| +| **Frontend** | React 18, TypeScript, Vite 5 | SPA with TanStack Query, React Router v6, Zod validation | +| **UI Framework** | shadcn/ui, Tailwind CSS | Radix UI primitives, responsive design, dark mode capable | +| **Backend** | Odoo 19, Python 3.11 | 15 custom modules, ORM-based data access, background job processing | +| **Database** | PostgreSQL 16 | Odoo-managed schema, full-text search, JSONB for flexible data | +| **LMS** | OpenEduCat | Odoo-native education modules: courses, batches, attendance, timetable | +| **AI -- Text Generation** | OpenAI GPT-4o, GPT-3.5-turbo | Exam content generation, grading, evaluation, training tips | +| **AI -- Speech-to-Text** | OpenAI Whisper (local, base model) | 4 instances, round-robin, 30s chunked transcription | +| **AI -- Text-to-Speech** | AWS Polly (Neural) | 11 English voices, MP3 output, sentence-boundary chunking | +| **AI -- Video** | ELAI | 7 AI avatars (male/female), ElevenLabs + Azure voices | +| **AI -- Detection** | GPTZero | AI-generated writing detection per submission | +| **AI -- Search** | FAISS + all-MiniLM-L6-v2 | Semantic similarity search over training tips knowledge base | +| **Deployment** | Docker Compose | Odoo + PostgreSQL containers, Nginx reverse proxy | +| **Version Control** | Gitea | Private repositories for frontend and backend | + +### 3.3 Deployment Architecture + +```mermaid +graph LR + subgraph production ["Production Server"] + Nginx["Nginx
Reverse Proxy
SSL Termination"] + FrontendContainer["Frontend Container
React SPA
Port 3000"] + OdooContainer["Odoo 19 Container
Python 3.11
Port 8069"] + DBContainer["PostgreSQL 16
Port 5432"] + end + + Browser["Browser"] -->|"HTTPS"| Nginx + Nginx -->|"/app/*"| FrontendContainer + Nginx -->|"/api/*"| OdooContainer + OdooContainer --> DBContainer + FrontendContainer -->|"REST API"| OdooContainer +``` + +### 3.4 Existing Assets Inventory + +| Asset | Count | Status | +|-------|-------|--------| +| Odoo custom modules | 15 | Deployed on staging | +| Backend REST API endpoints | 70+ | Functional, English/IELTS focused | +| Frontend pages (React) | 55+ | UI complete, mock data only | +| AI service integrations | 6 | Configured and tested | +| ELAI avatar profiles | 7 | Seeded in database | +| Training tips (FAISS) | 80 | Imported with embeddings | +| Odoo data models | 20+ | Schema deployed | + +--- + +## 4. Feature Modules Breakdown + +### 4.1 English Self Learning + +| Aspect | Detail | +|--------|--------| +| **Current State** | Backend complete: AI-powered generation for Reading, Listening, Writing, Speaking. Grading via GPT-4o with IELTS rubric. Polly TTS for listening audio. ELAI avatars for speaking. FAISS-based personalized training tips. Frontend UI built (mocked). | +| **Work Required** | Replace mock authentication with Odoo JWT. Wire all student-facing pages to backend APIs. Integrate AI grading display (scores, feedback, AI detection). Connect training/tips module. | +| **Dependencies** | Stable Odoo API (Phase 1 complete). Frontend auth working. | +| **Effort** | 6 person-weeks | +| **Risk** | Low -- both backend and frontend exist; integration is the main task. | + +### 4.2 Examinations + +| Aspect | Detail | +|--------|--------| +| **Current State** | Backend complete: exam CRUD, 5 module types (reading, listening, writing, speaking, level), AI generation endpoints, approval workflows. Frontend: exam editor, exam list, generation page, approval workflows (all mocked). | +| **Work Required** | Wire admin exam management pages to APIs. Connect AI generation UI to real endpoints. Implement exam-taking flow with timer and state persistence. Connect grading pipeline and results display. | +| **Dependencies** | English Self Learning module (shared components). | +| **Effort** | Included in English Self Learning estimate (shared codebase). | +| **Risk** | Low -- core exam engine is proven. | + +### 4.3 Mathematics + +| Aspect | Detail | +|--------|--------| +| **Current State** | No backend module exists. No frontend pages exist. The AI exam engine (GPT-based generation + grading) provides a reusable foundation. | +| **Work Required** | **Backend:** Create `encoach_math` Odoo module with Math-specific exam models, AI prompt templates for formula-based questions, numerical grading logic, and multi-format question types (MCQ, fill-in, short answer, calculation). **Frontend:** Create Math course pages, Math exam taking interface with formula rendering (KaTeX/MathJax), Math-specific grading display. | +| **Dependencies** | Core exam engine must be stable. AI prompts require testing with GPT-4o for mathematical accuracy. | +| **Effort** | 5 person-weeks | +| **Risk** | Medium -- AI generation of math questions requires careful prompt engineering for accuracy. Formula rendering on frontend adds complexity. | + +### 4.4 Information Technology + +| Aspect | Detail | +|--------|--------| +| **Current State** | No backend module exists. No frontend pages exist. Similar to Math, the exam engine provides a foundation. | +| **Work Required** | **Backend:** Create `encoach_it` Odoo module with IT-specific exam models, AI prompt templates for conceptual and practical IT questions (networking, databases, programming concepts, security), grading logic for MCQ and short-answer. **Frontend:** Create IT course pages, IT exam interface, IT-specific grading display. | +| **Dependencies** | Core exam engine must be stable. | +| **Effort** | 4 person-weeks | +| **Risk** | Low-Medium -- IT subjects are text-based (no formula complexity). AI generation of IT questions is straightforward with GPT-4o. | + +### 4.5 LMS (OpenEduCat) + +| Aspect | Detail | +|--------|--------| +| **Current State** | OpenEduCat modules will be provided and installed into Odoo 19. Frontend LMS pages exist and are fully built: student dashboard, courses, course detail, assignments, grades, attendance, timetable (student); courses, course builder, assignments, attendance, students, timetable (teacher); dashboard, courses, students, teachers, batches, batch detail, timetable, reports, settings (admin). All pages use mock data. | +| **Work Required** | **Backend:** Install OpenEduCat modules. Create REST API controller layer (`encoach_lms_api`) to expose OpenEduCat models as JSON endpoints matching the frontend's data expectations. Map OpenEduCat models to frontend data interfaces (Course, Module, Lesson, Batch, Timetable, Attendance, Grade). **Frontend:** Replace mock data imports with API calls using TanStack Query. Wire forms to create/update APIs. | +| **Dependencies** | OpenEduCat modules provided and compatible with Odoo 19. | +| **Effort** | 3 person-weeks | +| **Risk** | Medium -- OpenEduCat model compatibility with Odoo 19 must be verified. API mapping between OpenEduCat's data structures and the frontend's interfaces requires careful alignment. | + +### 4.6 Whitelabeling + +| Aspect | Detail | +|--------|--------| +| **Current State** | No whitelabeling support. Frontend uses CSS variables for theming (Tailwind + shadcn/ui) with `darkMode: ["class"]` configured but not activated. No branding configuration. | +| **Work Required** | **Backend:** Create `encoach_branding` model in Odoo for tenant branding settings (logo, primary color, secondary color, font, favicon, login background, email header). Expose branding API endpoint. **Frontend:** Create a branding provider that fetches configuration on app load and applies CSS variables dynamically. Configure logo, colors, and fonts from API. Add admin UI for branding management. | +| **Dependencies** | UTAS provides brand assets (logo, color palette, fonts). | +| **Effort** | 2 person-weeks | +| **Risk** | Low -- CSS variable-based theming is well-established. | + +### 4.7 Humanisation (AI Virtual Tutors) + +| Aspect | Detail | +|--------|--------| +| **Current State** | ELAI integration exists for speaking exam avatar videos. 7 avatars configured (Gia, Vadim, Orhan, Flora, Scarlett, Parker, Ethan) with ElevenLabs and Azure voices. Currently limited to presenting speaking exam questions. | +| **Work Required** | **Backend:** Extend ELAI integration to support tutoring mode -- avatars explain concepts, provide feedback narration, and guide students through exercises. Create endpoints for on-demand tutoring video generation per subject. **Frontend:** Build tutor interaction UI -- avatar selector, video player with synchronized content, interactive Q&A flow. Integrate across English, Math, and IT modules. | +| **Dependencies** | ELAI API capacity and cost model for increased video generation. Subject-specific scripts/prompts. | +| **Effort** | 3 person-weeks | +| **Risk** | Medium -- ELAI video generation time (1-3 minutes per video) affects real-time tutoring UX. Cost scales with usage. | + +### 4.8 SIS Integration + +| Aspect | Detail | +|--------|--------| +| **Current State** | No SIS integration exists. UTAS has an existing Student Information System that we must integrate with via API. | +| **Work Required** | **Discovery:** Obtain UTAS SIS API documentation, authentication method, and data schemas. **Backend:** Create `encoach_sis` Odoo module with SIS sync models, scheduled sync jobs (cron), and conflict resolution. Implement: student enrollment import, grade export, attendance sync, course catalog mapping. **Frontend:** SIS sync status dashboard for admin. Sync logs and error reporting. | +| **Dependencies** | UTAS provides SIS API documentation, test environment access, and a technical contact. | +| **Effort** | 4 person-weeks | +| **Risk** | High -- entirely dependent on UTAS SIS API quality, documentation completeness, and test environment availability. This is the single highest-risk module. | + +### 4.9 Ticketing + +| Aspect | Detail | +|--------|--------| +| **Current State** | Backend complete: `encoach_ticket` Odoo module with full CRUD, categorization, and status workflow. API endpoints exist (`/api/tickets`). Frontend: Tickets page exists in admin portal (mocked). | +| **Work Required** | Wire frontend tickets page to backend API. Add ticket creation flow for students and teachers. Add ticket assignment and resolution workflow for admin. | +| **Dependencies** | None -- self-contained module. | +| **Effort** | 1 person-week | +| **Risk** | Low -- both backend and frontend exist. | + +### 4.10 Effort Summary + +| Module | Person-Weeks | Wave | +|--------|-------------|------| +| English Self Learning + Examinations | 6 | Wave 1 | +| Mathematics | 5 | Wave 2 | +| Information Technology | 4 | Wave 2 | +| LMS (OpenEduCat) | 3 | Wave 2 | +| Ticketing | 1 | Wave 2 | +| Whitelabeling | 2 | Wave 3 | +| Humanisation | 3 | Wave 3 | +| SIS Integration | 4 | Wave 3 | +| **Total** | **28 person-weeks** | | + +With a team of 3 producing approximately 3 person-weeks per calendar week, the raw work is approximately 9-10 calendar weeks. Adding UAT cycles, amendments, and buffer, the overall calendar duration is 20 weeks (April 1 -- August 21). + +--- + +## 5. Development Phases + +The project is structured into three overlapping waves. Each wave follows a build-test-deploy cycle and produces a production-ready deliverable. + +### 5.1 Wave 1 -- Foundation (April 1 -- June 12) + +**Goal:** English Self Learning and Examinations modules live in production with real UTAS users. + +This wave transforms the existing backend and frontend from separate prototypes into an integrated, production-ready platform. + +#### Sprint Breakdown + +| Sprint | Dates | Focus | Deliverables | +|--------|-------|-------|-------------| +| **S1** | Apr 1-11 | **Stabilization & Auth** | Fix remaining backend bugs. Replace mock auth with Odoo JWT in React frontend. Set up API service layer. Configure production deployment pipeline. | +| **S2** | Apr 12-18 | **Core API Integration** | Wire student dashboard, courses, and profile pages. Implement exam list and exam detail APIs. Connect user management and entity management. | +| **S3** | Apr 19-25 | **Exam Engine Integration** | Wire exam-taking flow (reading, listening, writing, speaking). Connect AI generation endpoints to exam editor. Integrate timer, state persistence, and submission. **Staging demo to UTAS (Apr 24-25).** | +| **S4** | Apr 26 -- May 9 | **AI Features & Grading** | Wire AI grading pipeline (writing evaluation, speaking evaluation). Connect TTS audio playback. Connect ELAI avatar videos. Integrate training tips and personalized recommendations. Connect AI detection display. | +| **S5** | May 10-23 | **UAT Round 1** | Deploy to UTAS staging. Conduct UAT with UTAS staff and selected students. Collect feedback. Bug fixes and amendments. | +| **S6** | May 24 -- Jun 6 | **UAT Round 2 & Hardening** | Address UAT feedback. Performance optimization. Security review. Documentation. | +| **S7** | Jun 7-12 | **GO LIVE** | Production deployment. Monitoring setup. Handover to UTAS for English + Examinations. Post-launch support begins. | + +#### Wave 1 Exit Criteria + +- Students can register, log in, and complete full English practice sessions +- AI grading returns accurate band scores with detailed feedback for writing and speaking +- Teachers can generate, review, approve, and assign exams +- Admin can manage users, entities, classrooms, and assignments +- Training tips are personalized based on exam performance +- Platform is stable under expected UTAS load + +### 5.2 Wave 2 -- Expansion (May 17 -- July 24) + +**Goal:** Mathematics, Information Technology, LMS, and Ticketing modules live in production. + +Wave 2 begins during Wave 1's UAT phase, leveraging developers freed from active feature work. + +#### Sprint Breakdown + +| Sprint | Dates | Focus | Deliverables | +|--------|-------|-------|-------------| +| **S8** | May 17-30 | **Math & IT Backend** | Create `encoach_math` and `encoach_it` Odoo modules. Develop AI prompt templates for Math question generation (numerical, formula-based, word problems). Develop AI prompt templates for IT questions (conceptual, practical). Implement grading logic for non-essay formats. | +| **S9** | May 31 -- Jun 13 | **OpenEduCat & LMS API** | Install and configure OpenEduCat modules in Odoo 19. Create `encoach_lms_api` controller layer. Map OpenEduCat models to frontend data interfaces. Wire LMS admin pages (courses, batches, timetable, attendance). | +| **S10** | Jun 14-27 | **Math & IT Frontend + LMS Wiring** | Build Math exam interface with formula rendering. Build IT exam interface. Wire student and teacher LMS pages. Wire ticketing module. | +| **S11** | Jun 28 -- Jul 11 | **UAT Round 1** | Deploy Math, IT, LMS, Ticketing to staging. UAT with UTAS staff. Math AI accuracy testing. LMS workflow validation. | +| **S12** | Jul 12-18 | **Amendments** | Address UAT feedback. Fix Math/IT grading edge cases. LMS refinements. | +| **S13** | Jul 19-24 | **GO LIVE** | Production deployment of Math, IT, LMS, Ticketing. Post-launch support begins. | + +#### Wave 2 Exit Criteria + +- Math and IT exams generate accurately via AI with appropriate difficulty levels +- LMS (OpenEduCat) fully functional: courses, enrollment, attendance, timetable, gradebook +- Ticketing system operational for students and staff +- All modules integrated with Wave 1 platform (single sign-on, unified navigation) + +### 5.3 Wave 3 -- Integration (July 1 -- August 21) + +**Goal:** Whitelabeling, SIS integration, and Humanisation modules complete. Full platform go-live. + +Wave 3 begins during Wave 2's frontend phase, with SIS discovery starting earlier. + +#### Sprint Breakdown + +| Sprint | Dates | Focus | Deliverables | +|--------|-------|-------|-------------| +| **S14** | Jul 1-11 | **Whitelabeling + SIS Discovery** | Implement branding configuration backend and frontend. Apply UTAS brand assets. Begin SIS API discovery and documentation review with UTAS IT team. | +| **S15** | Jul 12-25 | **SIS Integration Development** | Build `encoach_sis` Odoo module. Implement student enrollment import. Implement grade export. Implement attendance sync. Build admin SIS dashboard. | +| **S16** | Jul 26 -- Aug 1 | **Humanisation** | Extend ELAI integration for tutoring mode. Build tutor interaction UI. Generate subject-specific avatar tutoring content. | +| **S17** | Aug 2-8 | **Integration Testing** | End-to-end testing of all 9 modules together. SIS sync validation with UTAS test data. Performance testing under load. Security audit. | +| **S18** | Aug 9-15 | **Final UAT** | Full platform UAT with UTAS. All modules tested together. Final amendments. | +| **S19** | Aug 16-21 | **Full Platform GO LIVE** | Production deployment of complete platform. All 9 modules active. Monitoring and support in place. | + +#### Wave 3 Exit Criteria + +- Platform displays UTAS branding throughout +- SIS syncs student enrollment, grades, and attendance bidirectionally +- AI virtual tutors available across English, Math, and IT +- All 9 modules function as an integrated platform +- Performance meets production requirements under expected load + +--- + +## 6. University Integration Plan + +### 6.1 Integration Architecture + +```mermaid +sequenceDiagram + participant SIS as UTAS SIS + participant Odoo as EnCoach Odoo 19 + participant React as EnCoach Frontend + participant Student as Student + + Note over SIS,Odoo: Enrollment Sync (SIS → EnCoach) + SIS->>Odoo: Student enrollment data (scheduled sync) + Odoo->>Odoo: Create/update student records + Odoo->>Odoo: Assign to courses and batches + + Note over Student,React: Daily Usage + Student->>React: Login, take courses, complete exams + React->>Odoo: API calls (exam, grading, attendance) + Odoo->>Odoo: Record grades, attendance, progress + + Note over Odoo,SIS: Grade & Attendance Export (EnCoach → SIS) + Odoo->>SIS: Grade export (scheduled sync) + Odoo->>SIS: Attendance export (scheduled sync) +``` + +### 6.2 Data Exchange Specification + +| Data Flow | Direction | Frequency | Format | +|-----------|-----------|-----------|--------| +| Student enrollment | SIS → EnCoach | On enrollment + daily sync | REST API / CSV import | +| Course catalog mapping | Bidirectional | On setup + manual sync | REST API | +| Exam grades | EnCoach → SIS | After grading + daily batch | REST API | +| Attendance records | EnCoach → SIS | End of day batch | REST API | +| Student status changes | SIS → EnCoach | Real-time webhook or daily | REST API | + +### 6.3 Authentication Strategy + +| Phase | Method | Detail | +|-------|--------|--------| +| **Initial (Wave 1-2)** | Odoo-native JWT | Students and staff authenticate directly with EnCoach. Credentials provisioned from SIS enrollment data. | +| **Full Integration (Wave 3)** | SSO via SAML/OAuth | If UTAS SIS supports SSO, students authenticate once at UTAS portal and access EnCoach seamlessly. Fallback to JWT if SSO is not available. | + +### 6.4 UTAS Responsibilities for Integration + +| Deliverable | Required By | Impact if Delayed | +|-------------|-------------|-------------------| +| SIS API documentation | June 15, 2026 | Blocks SIS module development; delays Wave 3 by equivalent duration | +| SIS test environment access | July 1, 2026 | Blocks integration testing | +| SIS technical contact (dedicated) | June 15, 2026 | Slows discovery and issue resolution | +| Brand assets (logo, colors, fonts) | June 15, 2026 | Blocks whitelabeling completion | +| UAT testers (3-5 staff, 10-20 students) | May 1, 2026 | Delays UAT cycles; risks quality | +| Math and IT course content (if not AI-generated) | May 15, 2026 | Delays Math/IT module population | +| Production server provisioning | August 1, 2026 | Blocks production deployment | + +--- + +## 7. Master Timeline + +### 7.1 Gantt Chart + +```mermaid +gantt + title EnCoach UTAS Deployment -- Master Timeline + dateFormat YYYY-MM-DD + axisFormat %d %b + + section Wave1_Foundation + S1_Stabilize_Auth :s1, 2026-04-01, 11d + S2_Core_API_Integration :s2, 2026-04-12, 7d + S3_Exam_Engine_Integration :s3, 2026-04-19, 7d + UTAS_Staging_Demo :milestone, m1, 2026-04-24, 0d + S4_AI_Features_Grading :s4, 2026-04-26, 14d + S5_UAT_Round_1 :s5, 2026-05-10, 14d + S6_UAT_Round_2_Hardening :s6, 2026-05-24, 14d + S7_English_Exams_GO_LIVE :s7, 2026-06-07, 5d + English_Exams_LIVE :milestone, m2, 2026-06-12, 0d + + section Wave2_Expansion + S8_Math_IT_Backend :s8, 2026-05-17, 14d + S9_OpenEduCat_LMS_API :s9, 2026-05-31, 14d + S10_Math_IT_LMS_Frontend :s10, 2026-06-14, 14d + S11_UAT_Math_IT_LMS :s11, 2026-06-28, 14d + S12_Amendments :s12, 2026-07-12, 7d + S13_Math_IT_LMS_GO_LIVE :s13, 2026-07-19, 5d + Math_IT_LMS_LIVE :milestone, m3, 2026-07-24, 0d + + section Wave3_Integration + S14_Whitelabeling_SIS_Discovery :s14, 2026-07-01, 11d + S15_SIS_Integration :s15, 2026-07-12, 14d + S16_Humanisation :s16, 2026-07-26, 7d + S17_Integration_Testing :s17, 2026-08-02, 7d + S18_Final_UAT :s18, 2026-08-09, 7d + S19_Full_Platform_GO_LIVE :s19, 2026-08-16, 5d + Full_Platform_LIVE :milestone, m4, 2026-08-21, 0d + + section PostLaunch + Post_Launch_Support :pls, 2026-08-21, 30d +``` + +### 7.2 Key Milestones + +| # | Milestone | Date | Gate Criteria | +|---|-----------|------|---------------| +| M0 | Project Kickoff | April 1, 2026 | Team assembled, scope agreed, environments ready | +| M1 | UTAS Staging Demo | April 24, 2026 | English + Exams functional on staging, basic end-to-end flow | +| M2 | Wave 1 GO LIVE | June 12, 2026 | English Self Learning + Examinations in production, 2 UAT rounds passed | +| M3 | Wave 2 GO LIVE | July 24, 2026 | Math + IT + LMS + Ticketing in production, UAT passed | +| M4 | Full Platform GO LIVE | August 21, 2026 | All 9 modules in production, SIS integration validated, full UAT passed | +| M5 | Post-Launch Support Ends | September 21, 2026 | 30-day support period complete, handover documentation delivered | + +### 7.3 Critical Path + +The critical path runs through: + +1. **Wave 1 auth stabilization** (blocks all frontend work) +2. **Wave 1 API integration** (blocks UAT and all subsequent waves) +3. **UTAS SIS API documentation delivery** (blocks Wave 3 SIS development) +4. **OpenEduCat module compatibility** (blocks LMS development) + +Any delay on items 1-2 cascades through the entire timeline. Items 3-4 are external dependencies that must be tracked proactively. + +--- + +## 8. Testing and Rollout Strategy + +### 8.1 Testing Stages + +```mermaid +graph LR + Dev["Developer Testing
Unit + Integration
Continuous"] --> Staging["Staging
System Integration
Per Sprint"] + Staging --> UAT["UAT with UTAS
Acceptance
Per Wave"] + UAT --> Perf["Performance Testing
Load + Stress
Pre-Go-Live"] + Perf --> Security["Security Audit
OWASP Top 10
Pre-Go-Live"] + Security --> GoLive["GO LIVE
Production
Per Wave"] +``` + +| Stage | Who | When | Scope | +|-------|-----|------|-------| +| **Developer Testing** | EnCoach dev team | Continuous | Unit tests, API integration tests, manual smoke tests per feature | +| **Staging Deployment** | EnCoach dev team | End of each sprint | Full platform on staging server, regression testing, cross-module integration | +| **UAT (User Acceptance)** | UTAS staff + students | 2 rounds per wave | Guided test scenarios, real-world usage, feedback collection | +| **Performance Testing** | EnCoach dev team | Before each go-live | Concurrent user simulation (target: 200 simultaneous users), API response times (<2s for standard, <60s for AI grading) | +| **Security Audit** | EnCoach architect | Before production go-live | Authentication review, authorization checks, API input validation, OWASP Top 10 | + +### 8.2 UAT Process + +Each wave includes two UAT rounds: + +**UAT Round 1 (2 weeks):** +1. EnCoach deploys to staging environment +2. UTAS UAT team receives test accounts and test scenarios document +3. UTAS testers execute scenarios and log issues via shared tracking system +4. EnCoach triages issues daily (critical = same day, high = 2 days, medium = next sprint) +5. Weekly status call between EnCoach and UTAS + +**UAT Round 2 (2 weeks):** +1. All critical and high issues from Round 1 resolved +2. Re-test of failed scenarios +3. Exploratory testing by UTAS testers +4. Go-live readiness assessment +5. Sign-off by UTAS project lead + +### 8.3 Go-Live Checklist (Per Wave) + +| # | Item | Owner | +|---|------|-------| +| 1 | All critical and high UAT issues resolved | EnCoach | +| 2 | Performance tests pass (response time, concurrent users) | EnCoach | +| 3 | Security audit complete, no critical findings | EnCoach | +| 4 | Production environment provisioned and configured | EnCoach + UTAS IT | +| 5 | DNS / domain configured (if applicable) | UTAS IT | +| 6 | SSL certificate installed | EnCoach + UTAS IT | +| 7 | Database backup and restore procedure tested | EnCoach | +| 8 | Monitoring and alerting configured | EnCoach | +| 9 | Rollback procedure documented and tested | EnCoach | +| 10 | UTAS project lead sign-off | UTAS | + +### 8.4 Rollback Plan + +Each go-live deployment includes a documented rollback procedure: + +1. **Database snapshot** taken immediately before production deployment +2. **Previous container images** tagged and retained (minimum 3 previous versions) +3. **Rollback trigger criteria:** >5% error rate, >10s average response time, data integrity issue +4. **Rollback execution time:** <15 minutes (Docker image swap + database restore) +5. **Communication:** UTAS IT notified within 5 minutes of rollback decision + +### 8.5 Post-Launch Support + +| Period | Coverage | Scope | +|--------|----------|-------| +| Week 1-2 after each go-live | Daily monitoring, same-day critical fixes | Bug fixes, performance tuning, user support | +| Week 3-4 after each go-live | Standard monitoring, 2-day response for non-critical | Bug fixes, minor enhancements from feedback | +| After Full Platform GO LIVE | 30-day support period (Aug 21 -- Sep 21) | Full platform support, knowledge transfer to UTAS IT | + +--- + +## 9. Team and Responsibilities + +### 9.1 EnCoach Team + +| Role | Responsibilities | +|------|-----------------| +| **Solution Architect** | System architecture decisions. University integration design. SIS API mapping. Security and performance oversight. Code review. UTAS stakeholder communication. Sprint planning. | +| **Senior Developer (AI-assisted)** | Backend development (Odoo modules). AI service integration. Math/IT module AI prompts. SIS integration implementation. Database design. API development. | +| **Full-Stack Developer (AI-assisted)** | Frontend development (React). API integration. UI/UX implementation. Whitelabeling. LMS frontend wiring. Testing. | + +### 9.2 UTAS Responsibilities + +| Role | Responsibilities | +|------|-----------------| +| **Project Sponsor** | Budget approval. Strategic decisions. Escalation resolution. | +| **Project Lead** | Scope sign-off. UAT coordination. Go-live approval. Weekly status reviews. | +| **IT Department** | SIS API documentation and test environment. Production server provisioning. Network and DNS configuration. SSL certificates. | +| **Subject Matter Experts** | Math and IT course content review. Exam quality validation. Training content verification. | +| **UAT Testers (3-5 staff, 10-20 students)** | Execute test scenarios. Log issues and feedback. Re-test after fixes. | + +### 9.3 Communication Plan + +| Activity | Frequency | Participants | Medium | +|----------|-----------|-------------|--------| +| Sprint status report | Weekly (Friday) | EnCoach team → UTAS Project Lead | Email + document | +| Demo / walkthrough | Bi-weekly | EnCoach team + UTAS stakeholders | Video call + screen share | +| UAT feedback review | Daily during UAT | EnCoach team + UTAS testers | Shared issue tracker | +| Escalation call | As needed | Architect + UTAS Project Lead | Video call | +| Go-live readiness review | Before each go-live | All stakeholders | Meeting | + +### 9.4 Escalation Path + +``` +Level 1: Developer → Solution Architect (same day) +Level 2: Solution Architect → UTAS Project Lead (1 business day) +Level 3: UTAS Project Lead → UTAS Project Sponsor (2 business days) +``` + +--- + +## 10. Risk Register + +| # | Risk | Probability | Impact | Mitigation | +|---|------|------------|--------|------------| +| R1 | **UTAS SIS API documentation delayed or incomplete** | High | High | Begin SIS discovery in Wave 2 (early July). Prepare alternative sync methods (CSV import/export) as fallback. Weekly check-ins with UTAS IT from June onwards. | +| R2 | **Math AI question generation inaccuracy** | Medium | Medium | Invest in thorough prompt engineering with GPT-4o. Build validation test suite with known-correct Math problems. Include human review step in exam approval workflow. | +| R3 | **OpenEduCat incompatibility with Odoo 19** | Medium | High | Test OpenEduCat installation early (Sprint S9). Identify compatibility issues before committing to LMS frontend work. Fallback: build minimal LMS models within existing Odoo modules. | +| R4 | **Frontend API integration slower than estimated** | Medium | Medium | Prioritize critical paths (auth, core pages) first. Use API contract testing to parallelize frontend and backend work. Leverage AI coding assistants to accelerate repetitive wiring. | +| R5 | **Team bandwidth constraints** | Medium | High | AI-assisted development (both developers use AI tools) effectively increases output by 1.5-2x. Strict scope management -- no feature creep. Clear sprint goals with daily standups. | +| R6 | **ELAI video generation latency for Humanisation** | Low | Medium | Pre-generate common tutoring videos. Cache frequently requested content. Set user expectations (1-3 min generation time). Consider alternative: pre-recorded avatar videos for standard content. | +| R7 | **Production infrastructure not ready on time** | Low | High | Request production server provisioning by August 1 (3 weeks before go-live). Test deployment procedure on staging first. Have cloud-based fallback plan. | +| R8 | **UTAS UAT testers unavailable or insufficient feedback** | Medium | Medium | Provide detailed test scenarios with step-by-step instructions. Offer guided UAT sessions. Establish minimum UAT participation requirements in the scope agreement. | + +--- + +## 11. Assumptions and Prerequisites + +### 11.1 Assumptions + +| # | Assumption | Impact if Invalid | +|---|-----------|-------------------| +| A1 | UTAS provides 3-5 staff and 10-20 students for UAT testing | Delays UAT; reduces quality assurance | +| A2 | OpenEduCat modules are compatible with Odoo 19 | Requires building custom LMS models; adds 3-4 weeks | +| A3 | UTAS SIS exposes REST APIs for enrollment, grades, and attendance | Requires alternative integration (CSV, database sync); adds 2-3 weeks | +| A4 | Math and IT course content is either provided by UTAS or generated by AI | If neither, course content creation becomes a separate workstream | +| A5 | The platform will be accessed via web browsers only (no mobile app) | Mobile app would be a separate project | +| A6 | Institutional licensing model (no per-student payment processing) | Payment gateway integration would add 2-3 weeks | +| A7 | Single UTAS deployment (not multi-university / multi-tenant initially) | Multi-tenant support would add architectural complexity | +| A8 | Internet connectivity at UTAS is sufficient for AI cloud services (OpenAI, ELAI, AWS Polly) | On-premise AI deployment would be a separate project | + +### 11.2 Prerequisites (with Deadlines) + +| # | Prerequisite | Owner | Deadline | Status | +|---|-------------|-------|----------|--------| +| P1 | Scope agreement signed | UTAS + EnCoach | March 31, 2026 | Pending | +| P2 | OpenEduCat modules provided | EnCoach / UTAS | May 15, 2026 | Pending | +| P3 | UTAS brand assets delivered | UTAS | June 15, 2026 | Pending | +| P4 | SIS API documentation provided | UTAS IT | June 15, 2026 | Pending | +| P5 | SIS test environment access granted | UTAS IT | July 1, 2026 | Pending | +| P6 | UAT test team identified | UTAS | May 1, 2026 | Pending | +| P7 | Production server provisioned | UTAS IT / EnCoach | August 1, 2026 | Pending | +| P8 | Math/IT course content available | UTAS SMEs | May 15, 2026 | Pending | + +--- + +## Appendix A: Module-to-API Mapping (Existing Backend Endpoints) + +The following 70+ API endpoints are already built and deployed in the Odoo 19 backend. These form the foundation for Wave 1 frontend integration. + +| Category | Endpoints | Count | +|----------|-----------|-------| +| Authentication | `/api/login`, `/api/logout`, `/api/reset`, `/api/reset/sendVerification` | 4 | +| Users | `/api/user`, `/api/users/update`, `/api/users/list`, `/api/users/` | 4 | +| Entities & Roles | `/api/entities`, `/api/roles`, `/api/permissions` | 7 | +| Exams | `/api/exam`, `/api/exam/ `, `/api/exam/ / `, `/api/exam/avatars` | 7 | +| AI Generation | `/api/exam/reading/*`, `/api/exam/listening/*`, `/api/exam/writing/*`, `/api/exam/speaking/*`, `/api/exam/level/generate` | 8 | +| Media | `/api/exam/listening/media`, `/api/exam/listening/transcribe`, `/api/exam/speaking/media`, `/api/transcribe` | 6 | +| Evaluations & Grading | `/api/evaluate/writing`, `/api/evaluate/speaking`, `/api/evaluate/interactiveSpeaking`, `/api/grading/multiple` | 6 | +| Classrooms | `/api/groups`, `/api/groups/ ` | 3 | +| Assignments | `/api/assignments`, `/api/assignments/ `, `/api/assignments/ /start`, release, archive | 8 | +| Sessions & Stats | `/api/sessions`, `/api/stats`, `/api/statistical` | 5 | +| Training | `/api/training`, `/api/training/ `, `/api/training/tips`, `/api/training/walkthrough` | 4 | +| Subscriptions | `/api/packages`, `/api/stripe`, `/api/paypal`, `/api/paymob` + webhooks | 7 | +| Registration | `/api/register`, `/api/code/ `, `/api/batch_users`, `/api/make_user` | 4 | +| Tickets | `/api/tickets`, `/api/tickets/` | 3 | +| Storage | `/api/storage`, `/api/storage/delete` | 2 | +| Approvals | `/api/approval-workflows`, `/api/approval-workflows/ ` | 3 | +| **Total** | | **~81** | + +--- + +## Appendix B: AI Service Configuration Summary + +| Service | Model | Key Config | Usage | +|---------|-------|-----------|-------| +| OpenAI GPT | gpt-4o (primary), gpt-3.5-turbo (secondary) | Temperature: 0.1 (grading), 0.7 (generation), 0.2 (summaries). Max tokens: 4,097. JSON response format. | Exam generation, grading, evaluation, training tips | +| Whisper | base (local, ~1GB) | 4 instances, round-robin, 16kHz mono, 30s chunks with overlap | Speaking transcription, audio-to-text | +| AWS Polly | Neural engine | 11 voices, MP3 output, 3,000 char chunks | Listening exam audio generation | +| ELAI | 7 avatars | ElevenLabs + Azure voices, fade_in animation | Speaking exam video, virtual tutoring | +| GPTZero | v2/predict/text | Per-sentence analysis, confidence scoring | Writing AI detection | +| FAISS | IndexFlatL2 + all-MiniLM-L6-v2 | Top-5 results, 7 category indices, 80 training tips | Personalized training recommendations | + +--- + +## Appendix C: Frontend Page Inventory (New React Application) + +| Portal | Pages | Key Features | +|--------|-------|-------------| +| **Student** | Dashboard, Courses, Course Detail, Assignments, Grades, Attendance, Timetable, Profile | 8 pages | +| **Teacher** | Dashboard, Courses, Course Builder, Course Edit, Assignments, Assignment Detail, Attendance, Students, Timetable, Profile | 10 pages | +| **Admin (LMS)** | Dashboard, Courses, Students, Teachers, Batches, Batch Detail, Timetable, Reports, Settings, Profile | 10 pages | +| **Admin (Platform)** | Platform Dashboard, Assignments, Exams List, Exam Structures, Rubrics, Generation, Approval Workflows, Users, Entities, Classrooms, Student Performance, Stats Corporate, Record, Vocabulary, Grammar, Payment Record, Tickets, Settings, Exam | 19 pages | +| **Auth** | Login, Register, Forgot Password | 3 pages | +| **Total** | | **~50+ pages** | + +--- + +*Document prepared by EnCoach Engineering Team. For questions or clarifications, contact the Solution Architect.* diff --git a/docs/UTAS_SYSTEM_SCOPE.md b/docs/UTAS_SYSTEM_SCOPE.md new file mode 100644 index 0000000..30f6189 --- /dev/null +++ b/docs/UTAS_SYSTEM_SCOPE.md @@ -0,0 +1,86 @@ +# EnCoach -- UTAS System Scope Definition + +**Date:** March 16, 2026 +**Project:** EnCoach University Deployment for UTAS + +--- + +## Core Modules + +### 1. Learning Management System (LMS) + +| Item | Detail | +|------|--------| +| **Engine** | OpenEduCat (Odoo 19 native modules) | +| **Frontend** | React 18 SPA -- student, teacher, and admin portals (50+ pages built) | +| **Capabilities** | Course catalog and enrollment, batch/cohort management, timetable scheduling, attendance tracking, gradebook, academic reporting | +| **Integration** | REST API bridge between OpenEduCat models and React frontend | +| **Subjects** | English Self Learning, Mathematics, Information Technology (extensible) | + +### 2. Exam Portal + +| Item | Detail | +|------|--------| +| **Exam Types** | Reading, Listening, Writing, Speaking, Level Test, Math, IT | +| **AI Generation** | GPT-4o generates exam content per subject and difficulty level | +| **AI Grading** | Automated grading for writing (GPT-4o + IELTS rubric), speaking (Whisper STT + GPT-4o), MCQ, short answer, numerical | +| **AI Detection** | GPTZero flags AI-generated student submissions | +| **Media** | AWS Polly TTS for listening audio, ELAI avatars for speaking video | +| **Workflow** | Exam creation, review, approval, assignment, timed sessions, grading, results | +| **Roles** | Admin creates/approves, teacher assigns, student takes, system grades | + +### 3. Student Onboarding + +| Item | Detail | +|------|--------| +| **Registration** | Self-registration or bulk import (CSV/batch) by admin | +| **SIS Sync** | Automatic student provisioning from UTAS Student Information System | +| **Enrollment** | Assignment to courses, batches, and classrooms upon registration | +| **Verification** | Email verification, registration code validation | +| **Roles** | Student, teacher, admin -- role-based access from first login | + +### 4. Knowledge Base + +| Item | Detail | +|------|--------| +| **Training Tips** | 80+ categorized tips (reading, writing, strategy, vocabulary, grammar) with FAISS semantic search | +| **Personalization** | After each exam, GPT analyzes performance and retrieves relevant tips via RAG (Retrieval-Augmented Generation) | +| **Walkthroughs** | Per-module guided learning paths | +| **AI Tutoring** | ELAI avatar virtual tutors provide video-based explanations and guidance (Humanisation) | +| **Content Scope** | English/IELTS (existing), Math and IT (to be developed) | + +### 5. Helpdesk System + +| Item | Detail | +|------|--------| +| **Ticketing** | Students and staff create support tickets with category, priority, and description | +| **Workflow** | Creation, assignment, status tracking (open, in-progress, resolved, closed) | +| **Roles** | Students/teachers submit; admin staff categorize, assign, and resolve | +| **Backend** | `encoach_ticket` Odoo module with full CRUD API (`/api/tickets`) | + +### 6. API Integration with SIS + +| Item | Detail | +|------|--------| +| **Direction** | Bidirectional: UTAS SIS <--> EnCoach | +| **Inbound (SIS to EnCoach)** | Student enrollment data, course catalog mapping, student status changes | +| **Outbound (EnCoach to SIS)** | Exam grades, attendance records, course completion status | +| **Method** | REST API (preferred) or CSV import/export (fallback) | +| **Frequency** | Real-time webhook (if available) or scheduled sync (daily batch) | +| **Prerequisite** | UTAS provides SIS API documentation, test environment, and technical contact | + +--- + +## Technology Stack Summary + +| Layer | Technology | +|-------|-----------| +| Frontend | React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui | +| Backend | Odoo 19, Python 3.11, PostgreSQL 16 | +| LMS | OpenEduCat (Odoo modules) | +| AI | OpenAI GPT-4o, Whisper, AWS Polly, ELAI, GPTZero, FAISS | +| Deployment | Docker Compose, Nginx | + +--- + +*EnCoach Engineering -- UTAS Deployment* diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..40f72cc --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,26 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "@typescript-eslint/no-unused-vars": "off", + }, + }, +); diff --git a/index.html b/index.html new file mode 100644 index 0000000..c54f102 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + EnCoach — IELTS & English Learning Platform + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..341879b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8215 @@ +{ + "name": "vite_react_shadcn_ts", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite_react_shadcn_ts", + "version": "0.0.0", + "dependencies": { + "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-context-menu": "^2.2.15", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-hover-card": "^1.1.14", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-menubar": "^1.1.15", + "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.7", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.5", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-toast": "^1.2.14", + "@radix-ui/react-toggle": "^1.1.9", + "@radix-ui/react-toggle-group": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.83.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^3.6.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.462.0", + "next-themes": "^0.3.0", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.61.1", + "react-resizable-panels": "^2.1.9", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.4", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.9", + "zod": "^3.25.76" + }, + "devDependencies": { + "@eslint/js": "^9.32.0", + "@tailwindcss/typography": "^0.5.16", + "@testing-library/jest-dom": "^6.6.0", + "@testing-library/react": "^16.0.0", + "@types/node": "^22.16.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react-swc": "^3.11.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.32.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^15.15.0", + "jsdom": "^20.0.3", + "lovable-tagger": "^1.1.13", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "typescript-eslint": "^8.38.0", + "vite": "^5.4.19", + "vitest": "^3.2.4" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "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.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "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.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.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/@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==", + "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==", + "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==", + "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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.11.tgz", + "integrity": "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.11", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz", + "integrity": "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.14", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", + "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz", + "integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.15.tgz", + "integrity": "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.14.tgz", + "integrity": "sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.15.tgz", + "integrity": "sha512-Z71C7LGD+YDYo3TV81paUs8f3Zbmkvg6VLRQpKYfzioOE6n7fOhA3ApK/V/2Odolxjoc4ENk8AYCjohCNayd5A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.13.tgz", + "integrity": "sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.7.tgz", + "integrity": "sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz", + "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", + "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", + "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", + "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", + "integrity": "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz", + "integrity": "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.10.tgz", + "integrity": "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-toggle": "1.1.9", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", + "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", + "integrity": "sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.23" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.13.2", + "@swc/core-darwin-x64": "1.13.2", + "@swc/core-linux-arm-gnueabihf": "1.13.2", + "@swc/core-linux-arm64-gnu": "1.13.2", + "@swc/core-linux-arm64-musl": "1.13.2", + "@swc/core-linux-x64-gnu": "1.13.2", + "@swc/core-linux-x64-musl": "1.13.2", + "@swc/core-win32-arm64-msvc": "1.13.2", + "@swc/core-win32-ia32-msvc": "1.13.2", + "@swc/core-win32-x64-msvc": "1.13.2" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.2.tgz", + "integrity": "sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.2.tgz", + "integrity": "sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.2.tgz", + "integrity": "sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.2.tgz", + "integrity": "sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.2.tgz", + "integrity": "sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.2.tgz", + "integrity": "sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.2.tgz", + "integrity": "sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.2.tgz", + "integrity": "sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.2.tgz", + "integrity": "sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.2.tgz", + "integrity": "sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz", + "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz", + "integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz", + "integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.83.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", + "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.38.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", + "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.27", + "@swc/core": "^1.12.11" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6 || ^7" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "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.30001726", + "electron-to-chromium": "^1.5.173", + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "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==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "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/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "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/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "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/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "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==", + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==", + "dev": true, + "license": "ISC" + }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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.4" + }, + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "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==", + "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.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "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/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "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/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lovable-tagger": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/lovable-tagger/-/lovable-tagger-1.1.13.tgz", + "integrity": "sha512-RBEYDxao7Xf8ya29L0cd+ocE7Gs80xPOIOwwck65Hoie8YDKViuXi3UYV14DoNWIvaJ7WVPf7SG3cc844nFqGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "tailwindcss": "^3.4.17" + }, + "peerDependencies": { + "vite": ">=5.0.0 <8.0.0" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/lovable-tagger/node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.462.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.462.0.tgz", + "integrity": "sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "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==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "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==", + "dev": true, + "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==", + "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==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next-themes": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", + "integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "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==", + "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/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "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==", + "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-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.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-hook-form": { + "version": "7.61.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.61.1.tgz", + "integrity": "sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-resizable-panels": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.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==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "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": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "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==", + "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-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "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/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "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==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.38.0.tgz", + "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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==", + "license": "MIT" + }, + "node_modules/vaul": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.9.tgz", + "integrity": "sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..04b2365 --- /dev/null +++ b/package.json @@ -0,0 +1,89 @@ +{ + "name": "vite_react_shadcn_ts", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:dev": "vite build --mode development", + "lint": "eslint .", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-context-menu": "^2.2.15", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-hover-card": "^1.1.14", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-menubar": "^1.1.15", + "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.7", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.5", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-toast": "^1.2.14", + "@radix-ui/react-toggle": "^1.1.9", + "@radix-ui/react-toggle-group": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.83.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^3.6.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.462.0", + "next-themes": "^0.3.0", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.61.1", + "react-resizable-panels": "^2.1.9", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.4", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.9", + "zod": "^3.25.76" + }, + "devDependencies": { + "@eslint/js": "^9.32.0", + "@testing-library/jest-dom": "^6.6.0", + "@testing-library/react": "^16.0.0", + "@tailwindcss/typography": "^0.5.16", + "@types/node": "^22.16.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react-swc": "^3.11.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.32.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^15.15.0", + "jsdom": "^20.0.3", + "lovable-tagger": "^1.1.13", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "typescript-eslint": "^8.38.0", + "vite": "^5.4.19", + "vitest": "^3.2.4" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3c01d69 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..fc5e9d9 Binary files /dev/null and b/public/logo.png differ diff --git a/public/placeholder.svg b/public/placeholder.svg new file mode 100644 index 0000000..e763910 --- /dev/null +++ b/public/placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..6018e70 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,14 @@ +User-agent: Googlebot +Allow: / + +User-agent: Bingbot +Allow: / + +User-agent: Twitterbot +Allow: / + +User-agent: facebookexternalhit +Allow: / + +User-agent: * +Allow: / diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..536d970 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,258 @@ +import { Toaster } from "@/components/ui/toaster"; +import { Toaster as Sonner } from "@/components/ui/sonner"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { AuthProvider } from "@/contexts/AuthContext"; +import ProtectedRoute from "@/components/ProtectedRoute"; +import StudentLayout from "@/components/StudentLayout"; +import TeacherLayout from "@/components/TeacherLayout"; +import AdminLmsLayout from "@/components/AdminLmsLayout"; +import Login from "@/pages/Login"; +import Register from "@/pages/Register"; +import ForgotPassword from "@/pages/ForgotPassword"; +// Original platform pages +import AdminDashboard from "@/pages/AdminDashboard"; +import UsersPage from "@/pages/UsersPage"; +import EntitiesPage from "@/pages/EntitiesPage"; +import AssignmentsPage from "@/pages/AssignmentsPage"; +import ExamsListPage from "@/pages/ExamsListPage"; +import ExamStructuresPage from "@/pages/ExamStructuresPage"; +import RubricsPage from "@/pages/RubricsPage"; +import GenerationPage from "@/pages/GenerationPage"; +import ApprovalWorkflowsPage from "@/pages/ApprovalWorkflowsPage"; +import ClassroomsPage from "@/pages/ClassroomsPage"; +import StudentPerformancePage from "@/pages/StudentPerformancePage"; +import StatsCorporatePage from "@/pages/StatsCorporatePage"; +import RecordPage from "@/pages/RecordPage"; +import VocabularyPage from "@/pages/VocabularyPage"; +import GrammarPage from "@/pages/GrammarPage"; +import PaymentRecordPage from "@/pages/PaymentRecordPage"; +import TicketsPage from "@/pages/TicketsPage"; +import SettingsPage from "@/pages/SettingsPage"; +import ProfilePage from "@/pages/ProfilePage"; +import ExamPage from "@/pages/ExamPage"; +// Student pages +import StudentDashboard from "@/pages/student/StudentDashboard"; +import StudentCourses from "@/pages/student/StudentCourses"; +import StudentCourseDetail from "@/pages/student/StudentCourseDetail"; +import StudentAssignments from "@/pages/student/StudentAssignments"; +import StudentGrades from "@/pages/student/StudentGrades"; +import StudentAttendance from "@/pages/student/StudentAttendance"; +import StudentTimetable from "@/pages/student/StudentTimetable"; +import StudentProfile from "@/pages/student/StudentProfile"; +// Adaptive learning pages +import SubjectSelection from "@/pages/student/SubjectSelection"; +import DiagnosticTest from "@/pages/student/DiagnosticTest"; +import ProficiencyProfile from "@/pages/student/ProficiencyProfile"; +import LearningPlanPage from "@/pages/student/LearningPlan"; +import TopicLearning from "@/pages/student/TopicLearning"; +// Teacher pages +import TeacherDashboard from "@/pages/teacher/TeacherDashboard"; +import TeacherCourses from "@/pages/teacher/TeacherCourses"; +import CourseBuilder from "@/pages/teacher/CourseBuilder"; +import TeacherAssignments from "@/pages/teacher/TeacherAssignments"; +import TeacherAssignmentDetail from "@/pages/teacher/TeacherAssignmentDetail"; +import TeacherAttendance from "@/pages/teacher/TeacherAttendance"; +import TeacherStudents from "@/pages/teacher/TeacherStudents"; +import TeacherTimetable from "@/pages/teacher/TeacherTimetable"; +import TeacherProfile from "@/pages/teacher/TeacherProfile"; +// Admin LMS pages +import AdminLmsDashboard from "@/pages/admin/AdminLmsDashboard"; +import AdminCourses from "@/pages/admin/AdminCourses"; +import AdminStudents from "@/pages/admin/AdminStudents"; +import AdminTeachers from "@/pages/admin/AdminTeachers"; +import AdminBatches from "@/pages/admin/AdminBatches"; +import AdminBatchDetail from "@/pages/admin/AdminBatchDetail"; +import AdminTimetable from "@/pages/admin/AdminTimetable"; +import AdminReports from "@/pages/admin/AdminReports"; +import AdminSettings from "@/pages/admin/AdminSettings"; +import AdminProfileLms from "@/pages/admin/AdminProfile"; +import TaxonomyManager from "@/pages/admin/TaxonomyManager"; +import ResourceManager from "@/pages/admin/ResourceManager"; +import AcademicYearManager from "@/pages/admin/AcademicYearManager"; +import DepartmentManager from "@/pages/admin/DepartmentManager"; +import AdmissionList from "@/pages/admin/AdmissionList"; +import AdmissionDetail from "@/pages/admin/AdmissionDetail"; +import AdmissionRegisterPage from "@/pages/admin/AdmissionRegisterPage"; +import InstitutionalExamSessions from "@/pages/admin/InstitutionalExamSessions"; +import MarksheetManager from "@/pages/admin/MarksheetManager"; +import AdminStudentLeave from "@/pages/admin/AdminStudentLeave"; +import AdminFees from "@/pages/admin/AdminFees"; +import AdminLessons from "@/pages/admin/AdminLessons"; +import AdminGradebook from "@/pages/admin/AdminGradebook"; +import AdminStudentProgress from "@/pages/admin/AdminStudentProgress"; +import AdminLibrary from "@/pages/admin/AdminLibrary"; +import AdminActivities from "@/pages/admin/AdminActivities"; +import AdminFacilities from "@/pages/admin/AdminFacilities"; +import AdmissionApplication from "@/pages/AdmissionApplication"; +import SubjectRegistrationPage from "@/pages/student/SubjectRegistrationPage"; +// Courseware pages +import CourseChapters from "@/pages/teacher/CourseChapters"; +import ChapterDetail from "@/pages/teacher/ChapterDetail"; +import AiWorkbench from "@/pages/teacher/AiWorkbench"; +import TeacherDiscussionBoard from "@/pages/teacher/TeacherDiscussionBoard"; +import TeacherAnnouncements from "@/pages/teacher/TeacherAnnouncements"; +import StudentChapterView from "@/pages/student/StudentChapterView"; +import StudentDiscussionBoard from "@/pages/student/StudentDiscussionBoard"; +import StudentAnnouncements from "@/pages/student/StudentAnnouncements"; +import StudentMessages from "@/pages/student/StudentMessages"; +import StudentJourney from "@/pages/student/StudentJourney"; +import FaqManager from "@/pages/admin/FaqManager"; +import NotificationRules from "@/pages/admin/NotificationRules"; +import ApprovalWorkflowConfig from "@/pages/admin/ApprovalWorkflowConfig"; +import RolesPermissions from "@/pages/admin/RolesPermissions"; +import AuthorityMatrix from "@/pages/admin/AuthorityMatrix"; +import UserRoles from "@/pages/admin/UserRoles"; +import OfficialExamAccess from "@/pages/OfficialExamAccess"; +import FaqPage from "@/pages/FaqPage"; +import NotFound from "@/pages/NotFound"; +import { queryClient } from "@/lib/query-client"; + +const App = () => ( ++ +); + +export default App; diff --git a/src/components/AdminLmsLayout.tsx b/src/components/AdminLmsLayout.tsx new file mode 100644 index 0000000..34594d0 --- /dev/null +++ b/src/components/AdminLmsLayout.tsx @@ -0,0 +1,354 @@ +import { Outlet, Link, useNavigate, useLocation } from "react-router-dom"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { + Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, + SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, + SidebarHeader, SidebarFooter, SidebarSeparator, useSidebar, +} from "@/components/ui/sidebar"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { NavLink } from "@/components/NavLink"; +import { useAuth } from "@/contexts/AuthContext"; +import NotificationDropdown from "@/components/NotificationDropdown"; +import AiAssistantDrawer from "@/components/ai/AiAssistantDrawer"; +import AiSearchBar from "@/components/ai/AiSearchBar"; +import { usePermissions } from "@/hooks/usePermissions"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, + BreadcrumbPage, BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { + GraduationCap, LogOut, User, Settings, LayoutDashboard, ClipboardList, + FileText, Layers, BookOpen, Wand2, GitBranch, Users, BarChart3, + Building2, History, CreditCard, Ticket, BookA, PenTool, + Calendar, ChevronDown, HelpCircle, LucideIcon, Target, FolderOpen, + CalendarDays, Landmark, UserPlus, ScrollText, Award, + HelpCircle as FaqIcon, Bell, Workflow, + CalendarOff, DollarSign, BookMarked, BarChartHorizontal, TrendingUp, + Library, Activity, Warehouse, UserCog, +} from "lucide-react"; +import React from "react"; + +// ============= Navigation Config ============= +interface NavItem { title: string; url: string; icon: LucideIcon } + +const overviewItems: NavItem[] = [ + { title: "Admin Dashboard", url: "/admin/dashboard", icon: LayoutDashboard }, + { title: "Platform Dashboard", url: "/admin/platform", icon: BarChart3 }, +]; + +const lmsItems: NavItem[] = [ + { title: "Courses", url: "/admin/courses", icon: BookOpen }, + { title: "Students", url: "/admin/students", icon: Users }, + { title: "Teachers", url: "/admin/teachers", icon: GraduationCap }, + { title: "Batches", url: "/admin/batches", icon: Layers }, + { title: "Timetable", url: "/admin/timetable", icon: Calendar }, + { title: "Reports", url: "/admin/reports", icon: BarChart3 }, +]; + +const academicItems: NavItem[] = [ + { title: "Assignments", url: "/admin/assignments", icon: ClipboardList }, + { title: "Exams List", url: "/admin/examsList", icon: FileText }, + { title: "Exam Structures", url: "/admin/exam-structures", icon: Layers }, + { title: "Rubrics", url: "/admin/rubrics", icon: BookOpen }, + { title: "Generation", url: "/admin/generation", icon: Wand2 }, + { title: "Approval Workflows", url: "/admin/approval-workflows", icon: GitBranch }, +]; + +const adaptiveItems: NavItem[] = [ + { title: "Taxonomy", url: "/admin/taxonomy", icon: Target }, + { title: "Resources", url: "/admin/resources", icon: FolderOpen }, +]; + +const institutionalItems: NavItem[] = [ + { title: "Academic Years", url: "/admin/academic-years", icon: CalendarDays }, + { title: "Departments", url: "/admin/departments", icon: Landmark }, + { title: "Admissions", url: "/admin/admissions", icon: UserPlus }, + { title: "Admission Register", url: "/admin/admission-register", icon: ScrollText }, + { title: "Exam Sessions", url: "/admin/exam-sessions", icon: FileText }, + { title: "Marksheets", url: "/admin/marksheets", icon: Award }, + { title: "Student Leave", url: "/admin/student-leave", icon: CalendarOff }, + { title: "Fees & Payments", url: "/admin/fees", icon: DollarSign }, + { title: "Lessons", url: "/admin/lessons", icon: BookMarked }, + { title: "Gradebook", url: "/admin/gradebook", icon: BarChartHorizontal }, + { title: "Student Progress", url: "/admin/student-progress", icon: TrendingUp }, + { title: "Library", url: "/admin/library", icon: Library }, + { title: "Activities", url: "/admin/activities", icon: Activity }, + { title: "Facilities", url: "/admin/facilities", icon: Warehouse }, +]; + +const managementItems: NavItem[] = [ + { title: "Users", url: "/admin/users", icon: Users }, + { title: "Entities", url: "/admin/entities", icon: Building2 }, + { title: "Classrooms", url: "/admin/classrooms", icon: GraduationCap }, + { title: "User Roles", url: "/admin/user-roles", icon: UserCog }, + { title: "Roles & Permissions", url: "/admin/roles-permissions", icon: Settings }, + { title: "Authority Matrix", url: "/admin/authority-matrix", icon: Workflow }, +]; + +const reportItems: NavItem[] = [ + { title: "Student Performance", url: "/admin/student-performance", icon: BarChart3 }, + { title: "Stats Corporate", url: "/admin/stats-corporate", icon: Building2 }, + { title: "Record", url: "/admin/record", icon: History }, +]; + +const trainingItems: NavItem[] = [ + { title: "Vocabulary", url: "/admin/training/vocabulary", icon: BookA }, + { title: "Grammar", url: "/admin/training/grammar", icon: PenTool }, +]; + +const configItems: NavItem[] = [ + { title: "FAQ Manager", url: "/admin/faq", icon: FaqIcon }, + { title: "Notification Rules", url: "/admin/notification-rules", icon: Bell }, + { title: "Approval Config", url: "/admin/approval-config", icon: Workflow }, +]; + +const supportItems: NavItem[] = [ + { title: "Payment Record", url: "/admin/payment-record", icon: CreditCard }, + { title: "Tickets", url: "/admin/tickets", icon: Ticket }, + { title: "Settings", url: "/admin/settings-platform", icon: Settings }, +]; + +// ============= Reusable sidebar group ============= +function SidebarNavGroup({ label, items }: { label: string; items: NavItem[] }) { + const { state } = useSidebar(); + const collapsed = state === "collapsed"; + + return ( ++ ++ + + ++ ++ {/* Auth routes */} + +} /> + } /> + } /> + {/* Public pages */} + } /> + } /> + } /> + } /> + } /> + + {/* Student routes */} + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* Teacher routes */} + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* Admin routes — unified: original platform + LMS + AI */} + }> + }> + {/* LMS Dashboard */} + } /> + {/* Original platform dashboard */} + } /> + {/* LMS pages */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* Original academic pages */} + } /> + } /> + } /> + } /> + } /> + } /> + {/* Original management pages */} + } /> + } /> + } /> + {/* Original report pages */} + } /> + } /> + } /> + {/* Training */} + } /> + } /> + {/* Support */} + } /> + } /> + } /> + } /> + } /> + } /> + {/* Institutional LMS pages */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* OpenEduCat module pages */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* Redirects */} + } /> + } /> + {/* Legacy route redirects */} + } /> + } /> + + + ); +} + +// ============= Breadcrumbs ============= +const routeLabels: Record{label} ++ ++ {items.map((item) => ( + ++ + ))} ++ ++ ++ {!collapsed && {item.title}} + = { + admin: "Admin", dashboard: "Dashboard", platform: "Platform", courses: "Courses", + students: "Students", teachers: "Teachers", batches: "Batches", timetable: "Timetable", + reports: "Reports", assignments: "Assignments", examsList: "Exams List", + "exam-structures": "Exam Structures", rubrics: "Rubrics", generation: "Generation", + "approval-workflows": "Approval Workflows", users: "Users", entities: "Entities", + classrooms: "Classrooms", "student-performance": "Student Performance", + "stats-corporate": "Stats Corporate", record: "Record", training: "Training", + vocabulary: "Vocabulary", grammar: "Grammar", "payment-record": "Payment Record", + tickets: "Tickets", "settings-platform": "Settings", settings: "Settings", + profile: "Profile", exam: "Exam", + "academic-years": "Academic Years", departments: "Departments", + admissions: "Admissions", "admission-register": "Admission Register", + "exam-sessions": "Exam Sessions", marksheets: "Marksheets", + faq: "FAQ Manager", "notification-rules": "Notification Rules", + "approval-config": "Approval Config", + "student-leave": "Student Leave", fees: "Fees & Payments", lessons: "Lessons", + gradebook: "Gradebook", "student-progress": "Student Progress", + library: "Library", activities: "Activities", facilities: "Facilities", +}; + +function AppBreadcrumbs() { + const location = useLocation(); + const segments = location.pathname.split("/").filter(Boolean); + + return ( + + + ); +} + +// ============= Main Layout ============= +export default function AdminLmsLayout() { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + + const initials = user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??"; + + const handleLogout = async () => { + await logout(); + navigate("/login"); + }; + + return ( ++ ++ + {segments.map((seg, i) => { + const path = "/" + segments.slice(0, i + 1).join("/"); + const label = routeLabels[seg] || seg.charAt(0).toUpperCase() + seg.slice(1); + const isLast = i === segments.length - 1; + return ( +Home ++ + ); + })} ++ + {isLast ? +{label} :{label} } ++ + ); +} + +// ============= Admin Sidebar ============= +function AdminSidebar() { + const { state } = useSidebar(); + const collapsed = state === "collapsed"; + const { user } = useAuth(); + const { hasAnyPermission, isAdmin } = usePermissions(); + + const showManagement = isAdmin || hasAnyPermission(["view_entities", "view_students", "view_teachers"]); + const showReports = isAdmin || hasAnyPermission(["view_statistics", "view_student_performance", "view_entity_statistics"]); + const showPayments = isAdmin || hasAnyPermission(["view_payment_record", "pay_entity"]); + + return ( ++++ +++ ++++ + + +++ + + ++ + ++ +++{user?.name ?? "User"}
+{user?.email ?? ""}
++ navigate("/admin/profile")}> + +Profile + navigate("/admin/settings-platform")}> + +Settings + + + +Logout + + ++ + + + Need help? + + + + ); +} diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx new file mode 100644 index 0000000..52751a1 --- /dev/null +++ b/src/components/AppLayout.tsx @@ -0,0 +1,145 @@ +import { Outlet, useLocation, Link, useNavigate } from "react-router-dom"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "@/components/AppSidebar"; +import { + Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, + BreadcrumbPage, BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Ticket, Settings, User, LogOut, HelpCircle } from "lucide-react"; +import React from "react"; +import AiAssistantDrawer from "@/components/ai/AiAssistantDrawer"; +import AiSearchBar from "@/components/ai/AiSearchBar"; + +const routeLabels: Record+ ++++ {!collapsed && ( + + En + Coach + + )} +
+ + ++ + + + + {showManagement && } + {showReports && } + + + + + ++ ++ ++ Training + {!collapsed && +} + + ++ ++ {trainingItems.map((item) => ( + ++ + ))} ++ ++ ++ {!collapsed && {item.title}} + i.url !== "/admin/payment-record")} /> + + + ++++ {user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??"} ++ {!collapsed && ( ++ {user?.name ?? "User"} + {user?.email ?? ""} ++ )} += { + "dashboard": "Dashboard", + "admin": "Admin", + "assignments": "Assignments", + "examsList": "Exams List", + "exam-structures": "Exam Structures", + "rubrics": "Rubrics", + "generation": "Exam Generation", + "approval-workflows": "Approval Workflows", + "users": "Users", + "entities": "Entities", + "classrooms": "Classrooms", + "student-performance": "Student Performance", + "stats-corporate": "Stats Corporate", + "record": "Record", + "training": "Training", + "vocabulary": "Vocabulary", + "grammar": "Grammar", + "payment-record": "Payment Record", + "tickets": "Tickets", + "settings": "Settings", + "profile": "Profile", + "exam": "Exam", + "tutor": "Tutor", +}; + +function AppBreadcrumbs() { + const location = useLocation(); + const segments = location.pathname.split("/").filter(Boolean); + + return ( + + + ); +} + +export default function AppLayout() { + const navigate = useNavigate(); + + return ( ++ ++ + {segments.map((seg, i) => { + const path = "/" + segments.slice(0, i + 1).join("/"); + const label = routeLabels[seg] || seg.charAt(0).toUpperCase() + seg.slice(1); + const isLast = i === segments.length - 1; + return ( ++ Home + ++ + ); + })} ++ + {isLast ? ( + +{label} + ) : ( ++ {label} + + )} ++ + ); +} diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx new file mode 100644 index 0000000..f557045 --- /dev/null +++ b/src/components/AppSidebar.tsx @@ -0,0 +1,158 @@ +import { + LayoutDashboard, ClipboardList, FileText, Layers, BookOpen, Wand2, + GitBranch, Users, BarChart3, Building2, History, GraduationCap, + CreditCard, Ticket, Settings, User, BookA, PenTool, LogOut +} from "lucide-react"; +import { NavLink } from "@/components/NavLink"; +import { useLocation } from "react-router-dom"; +import { + Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, + SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, + SidebarHeader, SidebarFooter, SidebarSeparator, useSidebar, +} from "@/components/ui/sidebar"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { ChevronDown } from "lucide-react"; + +const mainItems = [ + { title: "Dashboard", url: "/dashboard/admin", icon: LayoutDashboard }, + { title: "Assignments", url: "/assignments", icon: ClipboardList }, + { title: "Exams List", url: "/examsList", icon: FileText }, + { title: "Exam Structures", url: "/exam-structures", icon: Layers }, + { title: "Rubrics", url: "/rubrics", icon: BookOpen }, + { title: "Generation", url: "/generation", icon: Wand2 }, + { title: "Approval Workflows", url: "/approval-workflows", icon: GitBranch }, +]; + +const managementItems = [ + { title: "Users", url: "/users", icon: Users }, + { title: "Entities", url: "/entities", icon: Building2 }, + { title: "Classrooms", url: "/classrooms", icon: GraduationCap }, +]; + +const reportItems = [ + { title: "Student Performance", url: "/student-performance", icon: BarChart3 }, + { title: "Stats Corporate", url: "/stats-corporate", icon: Building2 }, + { title: "Record", url: "/record", icon: History }, +]; + +const trainingItems = [ + { title: "Vocabulary", url: "/training/vocabulary", icon: BookA }, + { title: "Grammar", url: "/training/grammar", icon: PenTool }, +]; + +const supportItems = [ + { title: "Payment Record", url: "/payment-record", icon: CreditCard }, + { title: "Tickets", url: "/tickets", icon: Ticket }, + { title: "Settings", url: "/settings", icon: Settings }, +]; + +function SidebarNavGroup({ label, items }: { label: string; items: typeof mainItems }) { + const { state } = useSidebar(); + const collapsed = state === "collapsed"; + const location = useLocation(); + + return ( ++++ +++ ++++ + + + + +++ ++ + ++ +++Admin User
+admin@encoach.com
++ navigate("/profile")}> + +Profile + navigate("/settings")}> + +Settings + + navigate("/login")}> + +Logout + + ++ + {/* Floating help button */} + + + Need help? + + + + ); +} + +export function AppSidebar() { + const { state } = useSidebar(); + const collapsed = state === "collapsed"; + const location = useLocation(); + + return ( +{label} ++ ++ {items.map((item) => ( + ++ + ))} ++ ++ ++ {!collapsed && {item.title}} + + + ); +} diff --git a/src/components/NavLink.tsx b/src/components/NavLink.tsx new file mode 100644 index 0000000..a561a95 --- /dev/null +++ b/src/components/NavLink.tsx @@ -0,0 +1,28 @@ +import { NavLink as RouterNavLink, NavLinkProps } from "react-router-dom"; +import { forwardRef } from "react"; +import { cn } from "@/lib/utils"; + +interface NavLinkCompatProps extends Omit+ + +++++ {!collapsed && ( + + EnCoach + + )} ++ + + + + ++ + + + + + ++ ++ ++ Training + {!collapsed && +} + + ++ ++ {trainingItems.map((item) => ( + ++ + ))} ++ ++ ++ {!collapsed && {item.title}} + + + + + +++++ {!collapsed && ( ++ + Admin User + admin@encoach.com ++ )} +{ + className?: string; + activeClassName?: string; + pendingClassName?: string; +} + +const NavLink = forwardRef ( + ({ className, activeClassName, pendingClassName, to, ...props }, ref) => { + return ( + + cn(className, isActive && activeClassName, isPending && pendingClassName) + } + {...props} + /> + ); + }, +); + +NavLink.displayName = "NavLink"; + +export { NavLink }; diff --git a/src/components/NotificationDropdown.tsx b/src/components/NotificationDropdown.tsx new file mode 100644 index 0000000..caa9f8d --- /dev/null +++ b/src/components/NotificationDropdown.tsx @@ -0,0 +1,36 @@ +import { Bell } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +export default function NotificationDropdown() { + const notifications: { id: string; title: string; message: string; time: string; read: boolean; type: string }[] = []; + const unread = notifications.filter((n) => !n.read).length; + + return ( + + + ); +} diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..e97752c --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,64 @@ +import { Navigate, Outlet } from "react-router-dom"; +import { useAuth } from "@/contexts/AuthContext"; +import { usePermissions } from "@/hooks/usePermissions"; +import type { UserRole } from "@/types/auth"; + +interface Props { + allowedRoles?: UserRole[]; + requiredPermissions?: string[]; +} + +function getRoleDashboard(role: UserRole): string { + switch (role) { + case "student": + return "/student/dashboard"; + case "teacher": + return "/teacher/dashboard"; + case "admin": + case "developer": + return "/admin/dashboard"; + case "corporate": + case "mastercorporate": + case "agent": + return "/admin/platform"; + default: + return "/login"; + } +} + +export default function ProtectedRoute({ allowedRoles, requiredPermissions }: Props) { + const { user, isAuthenticated, isLoading } = useAuth(); + const { hasAllPermissions, isLoading: permissionsLoading } = usePermissions(); + + if (isLoading) { + return ( ++ + ++ +Notifications++ {notifications.slice(0, 4).map((n) => ( + + {n.title} + {n.message} + {n.time} + + ))} ++ ++ ); + } + + if (!isAuthenticated || !user) { + return; + } + + if (allowedRoles && !allowedRoles.includes(user.user_type)) { + return ; + } + + if (requiredPermissions && requiredPermissions.length > 0) { + if (permissionsLoading) { + return ( + + ++ ); + } + + if (!hasAllPermissions(requiredPermissions)) { + return; + } + } + + return ; +} diff --git a/src/components/RoleLayout.tsx b/src/components/RoleLayout.tsx new file mode 100644 index 0000000..6c1943f --- /dev/null +++ b/src/components/RoleLayout.tsx @@ -0,0 +1,151 @@ +import { Outlet, Link, useNavigate, useLocation } from "react-router-dom"; +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { + Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, + SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, + SidebarHeader, SidebarFooter, SidebarSeparator, useSidebar, +} from "@/components/ui/sidebar"; +import { NavLink } from "@/components/NavLink"; +import { useAuth, UserRole } from "@/contexts/AuthContext"; +import NotificationDropdown from "@/components/NotificationDropdown"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { LogOut, User, Settings, LucideIcon } from "lucide-react"; +import React from "react"; + +export interface NavItem { + title: string; + url: string; + icon: LucideIcon; +} + +export interface NavGroup { + label: string; + items: NavItem[]; +} + +interface RoleLayoutProps { + navGroups: NavGroup[]; + role: UserRole; +} + +function SidebarNav({ navGroups }: { navGroups: NavGroup[] }) { + const { state } = useSidebar(); + const collapsed = state === "collapsed"; + + return ( + <> + {navGroups.map((group) => ( + + + ))} + > + ); +} + +export default function RoleLayout({ navGroups, role }: RoleLayoutProps) { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + + const initials = user?.name?.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase() ?? "??"; + + const handleLogout = async () => { + await logout(); + navigate("/login"); + }; + + return ( +{group.label} ++ ++ {group.items.map((item) => ( + ++ + ))} ++ ++ ++ {!collapsed && {item.title}} + + + ); +} diff --git a/src/components/StudentLayout.tsx b/src/components/StudentLayout.tsx new file mode 100644 index 0000000..3fc64e3 --- /dev/null +++ b/src/components/StudentLayout.tsx @@ -0,0 +1,51 @@ +import RoleLayout, { NavGroup } from "./RoleLayout"; +import { + LayoutDashboard, BookOpen, ClipboardList, BarChart3, + CalendarCheck, Calendar, User, Target, GraduationCap, ListChecks, + MessageSquare, Megaphone, Mail, TrendingUp, +} from "lucide-react"; + +const navGroups: NavGroup[] = [ + { + label: "Learning", + items: [ + { title: "Dashboard", url: "/student/dashboard", icon: LayoutDashboard }, + { title: "My Courses", url: "/student/courses", icon: BookOpen }, + { title: "Subject Registration", url: "/student/subject-registration", icon: ListChecks }, + { title: "Assignments", url: "/student/assignments", icon: ClipboardList }, + ], + }, + { + label: "Adaptive Learning", + items: [ + { title: "My Subjects", url: "/student/subjects", icon: Target }, + ], + }, + { + label: "Progress", + items: [ + { title: "Grades", url: "/student/grades", icon: BarChart3 }, + { title: "Attendance", url: "/student/attendance", icon: CalendarCheck }, + { title: "Timetable", url: "/student/timetable", icon: Calendar }, + { title: "My Journey", url: "/student/journey", icon: TrendingUp }, + ], + }, + { + label: "Communication", + items: [ + { title: "Discussions", url: "/student/discussions", icon: MessageSquare }, + { title: "Messages", url: "/student/messages", icon: Mail }, + { title: "Announcements", url: "/student/announcements", icon: Megaphone }, + ], + }, + { + label: "Account", + items: [ + { title: "Profile", url: "/student/profile", icon: User }, + ], + }, +]; + +export default function StudentLayout() { + return+++ + ++ ++++ + En + Coach + +
+ + ++ + + ++++ {initials} +++ {user?.name} + {user?.email} +++++ ++++ {role} Portal + +++ + ++ + ++ +++{user?.name}
+{user?.email}
++ navigate(`/${role}/profile`)}> + +Profile + + + +Logout + + ++ ; +} diff --git a/src/components/TeacherLayout.tsx b/src/components/TeacherLayout.tsx new file mode 100644 index 0000000..caeccae --- /dev/null +++ b/src/components/TeacherLayout.tsx @@ -0,0 +1,42 @@ +import RoleLayout, { NavGroup } from "./RoleLayout"; +import { + LayoutDashboard, BookOpen, ClipboardList, FileText, + CalendarCheck, Users, Calendar, User, MessageSquare, + Megaphone, Wand2, +} from "lucide-react"; + +const navGroups: NavGroup[] = [ + { + label: "Teaching", + items: [ + { title: "Dashboard", url: "/teacher/dashboard", icon: LayoutDashboard }, + { title: "Courses", url: "/teacher/courses", icon: BookOpen }, + { title: "Assignments", url: "/teacher/assignments", icon: ClipboardList }, + ], + }, + { + label: "Management", + items: [ + { title: "Attendance", url: "/teacher/attendance", icon: CalendarCheck }, + { title: "Students", url: "/teacher/students", icon: Users }, + { title: "Timetable", url: "/teacher/timetable", icon: Calendar }, + ], + }, + { + label: "Communication", + items: [ + { title: "Discussions", url: "/teacher/discussions", icon: MessageSquare }, + { title: "Announcements", url: "/teacher/announcements", icon: Megaphone }, + ], + }, + { + label: "Account", + items: [ + { title: "Profile", url: "/teacher/profile", icon: User }, + ], + }, +]; + +export default function TeacherLayout() { + return ; +} diff --git a/src/components/ai/AiAlertBanner.tsx b/src/components/ai/AiAlertBanner.tsx new file mode 100644 index 0000000..e14ec37 --- /dev/null +++ b/src/components/ai/AiAlertBanner.tsx @@ -0,0 +1,80 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { AlertTriangle, X, Sparkles, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { analyticsService } from "@/services/analytics.service"; + +export default function AiAlertBanner() { + const [dismissedIds, setDismissedIds] = useState >(() => new Set()); + const [errorDismissed, setErrorDismissed] = useState(false); + + const { data: alerts, isLoading, isError, error } = useQuery({ + queryKey: ["ai", "alerts"], + queryFn: () => analyticsService.getAlerts(), + }); + + const visible = alerts?.filter((a) => !dismissedIds.has(a.id)) ?? []; + + if (isLoading) { + return ( + ++ ); + } + + if (isError && !errorDismissed) { + return ( ++ Loading alerts…
+++ ); + } + + if (isError && errorDismissed) return null; + + if (!alerts?.length) { + return ( ++ ++ ++
+Alerts unavailable + {error instanceof Error ? error.message : "Could not load alerts."}
+++ ); + } + + if (!visible.length) return null; + + return ( ++ No AI alerts right now.
++ {visible.map((alert) => ( ++ ); +} diff --git a/src/components/ai/AiAssistantDrawer.tsx b/src/components/ai/AiAssistantDrawer.tsx new file mode 100644 index 0000000..05ccaf7 --- /dev/null +++ b/src/components/ai/AiAssistantDrawer.tsx @@ -0,0 +1,139 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { useLocation } from "react-router-dom"; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Sparkles, Send, Loader2 } from "lucide-react"; +import { coachingService } from "@/services/coaching.service"; +import { useToast } from "@/hooks/use-toast"; + +const quickActions = [ + "Platform health summary", + "Show dropout risks", + "Compare course performance", + "Suggest batch improvements", +]; + +export default function AiAssistantDrawer() { + const [open, setOpen] = useState(false); + const [messages, setMessages] = useState<{ role: "user" | "ai"; text: string }[]>([]); + const [input, setInput] = useState(""); + const location = useLocation(); + const { toast } = useToast(); + + const chatMutation = useMutation({ + mutationFn: (message: string) => + coachingService.chat({ message, context: { page: location.pathname } }), + onSuccess: (data) => { + setMessages((prev) => [...prev, { role: "ai", text: data.message }]); + }, + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Could not get a reply", + description: err.message || "Something went wrong. Try again.", + }); + setMessages((prev) => [ + ...prev, + { + role: "ai", + text: "Sorry, I could not reach the assistant. Please try again in a moment.", + }, + ]); + }, + }); + + const handleSend = (text: string) => { + if (!text.trim() || chatMutation.isPending) return; + const userMsg = text.trim(); + setMessages((prev) => [...prev, { role: "user", text: userMsg }]); + setInput(""); + chatMutation.mutate(userMsg); + }; + + return ( + <> + + +++ ))} ++ ++ ++
+{alert.title} + {alert.description}
++ + > + ); +} diff --git a/src/components/ai/AiBatchOptimizer.tsx b/src/components/ai/AiBatchOptimizer.tsx new file mode 100644 index 0000000..7a10079 --- /dev/null +++ b/src/components/ai/AiBatchOptimizer.tsx @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Sparkles, Loader2 } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { analyticsService } from "@/services/analytics.service"; + +interface Props { + batchId?: number; +} + +export default function AiBatchOptimizer({ batchId }: Props) { + const [open, setOpen] = useState(false); + const { toast } = useToast(); + + const mutation = useMutation({ + mutationFn: (id: number) => analyticsService.getBatchOptimization(id), + onError: (err: Error) => { + toast({ + title: "Optimization failed", + description: err.message || "Could not analyze this batch.", + variant: "destructive", + }); + }, + }); + + const handleOpen = () => { + if (batchId == null) { + toast({ + title: "No batch selected", + description: "Choose a batch to analyze, or open this from a batch detail page.", + variant: "destructive", + }); + return; + } + mutation.reset(); + setOpen(true); + mutation.mutate(batchId); + }; + + const handleApply = () => { + toast({ title: "Suggestion Applied", description: "Batch split recommendation has been saved successfully." }); + setOpen(false); + }; + + const onOpenChange = (next: boolean) => { + setOpen(next); + if (!next) mutation.reset(); + }; + + const suggestions = mutation.data ?? []; + const showResults = !mutation.isPending && !mutation.isError && suggestions.length > 0; + const showEmpty = !mutation.isPending && !mutation.isError && mutation.isSuccess && suggestions.length === 0; + + return ( + <> + + + > + ); +} diff --git a/src/components/ai/AiCreationAssistant.tsx b/src/components/ai/AiCreationAssistant.tsx new file mode 100644 index 0000000..84af4b7 --- /dev/null +++ b/src/components/ai/AiCreationAssistant.tsx @@ -0,0 +1,215 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Sparkles, Loader2 } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; +import { lmsService } from "@/services/lms.service"; + +interface Props { + type: "course" | "assignment" | "batch" | "rubric" | "exam" | "student" | "teacher"; + trigger?: React.ReactNode; + onGenerated?: (data: any) => void; +} + +const typeLabels: Record+ ++ + ++ ++ EnCoach AI Assistant + + {quickActions.map((action) => ( + + ))} ++ ++ {messages.length === 0 && ( ++ +++ )} + {messages.map((msg, i) => ( ++ Ask me anything about the platform,
+or click a quick action above.
++ {msg.role === "ai" && ( ++ ))} + {chatMutation.isPending && ( ++ )} + {msg.text} + ++ )} ++ Thinking... + + setInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSend(input)} + /> + ++= { + course: "Course", assignment: "Assignment", batch: "Batch", rubric: "Rubric", + exam: "Exam", student: "Student Plan", teacher: "Teacher Assignment", +}; + +export default function AiCreationAssistant({ type, trigger, onGenerated }: Props) { + const [open, setOpen] = useState(false); + const [prompt, setPrompt] = useState(""); + const [level, setLevel] = useState("b2"); + const [subjectId, setSubjectId] = useState(""); + const { toast } = useToast(); + + const generateCourseMutation = useMutation({ + mutationFn: (payload: { title: string; subject_id?: number; level?: string }) => + lmsService.aiGenerateCourse(payload), + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Generation failed", + description: err.message || "Could not generate course outline.", + }); + }, + }); + + const handleGenerate = () => { + if (type !== "course") { + toast({ + variant: "destructive", + title: "Not available", + description: "Live AI generation is wired for courses only. Use the course type to generate an outline.", + }); + return; + } + const title = prompt.trim() || "Untitled course"; + const sid = subjectId.trim() ? Number(subjectId) : undefined; + generateCourseMutation.mutate({ + title, + ...(sid !== undefined && !Number.isNaN(sid) ? { subject_id: sid } : {}), + level, + }); + }; + + const handleApply = () => { + if (!generateCourseMutation.data) return; + onGenerated?.(generateCourseMutation.data); + toast({ + title: `AI ${typeLabels[type]} Applied`, + description: `The AI-generated ${type} has been applied to your form.`, + }); + setOpen(false); + generateCourseMutation.reset(); + }; + + return ( + <> + {trigger ? ( + setOpen(true)}>{trigger}+ ) : ( + + )} + + + > + ); +} diff --git a/src/components/ai/AiGeneratorModal.tsx b/src/components/ai/AiGeneratorModal.tsx new file mode 100644 index 0000000..52f580c --- /dev/null +++ b/src/components/ai/AiGeneratorModal.tsx @@ -0,0 +1,207 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Sparkles, Loader2, Trash2 } from "lucide-react"; +import type { ExamModule } from "@/types"; +import { generationService } from "@/services/generation.service"; +import { useToast } from "@/hooks/use-toast"; + +type ExerciseRow = { title?: string; description?: string; marks?: number }; + +function exerciseLabel(ex: unknown, index: number): ExerciseRow { + if (ex && typeof ex === "object") { + const o = ex as Record; + return { + title: typeof o.title === "string" ? o.title : `Exercise ${index + 1}`, + description: typeof o.description === "string" ? o.description : undefined, + marks: typeof o.marks === "number" ? o.marks : typeof o.marks === "string" ? Number(o.marks) : undefined, + }; + } + return { title: String(ex) }; +} + +export default function AiGeneratorModal() { + const [open, setOpen] = useState(false); + const [moduleType, setModuleType] = useState ("reading"); + const [difficulty, setDifficulty] = useState("b2"); + const [count, setCount] = useState(5); + const [topic, setTopic] = useState(""); + const [localExercises, setLocalExercises] = useState (null); + const { toast } = useToast(); + + const generateMutation = useMutation({ + mutationFn: () => + generationService.generate(moduleType, { + title: topic.trim() || `${moduleType} practice set`, + difficulty, + count, + }), + onSuccess: (res) => { + setLocalExercises(Array.isArray(res.exercises) ? res.exercises : []); + }, + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Generation failed", + description: err.message || "Could not generate exercises.", + }); + }, + }); + + const handleGenerate = () => { + setLocalExercises(null); + generateMutation.mutate(); + }; + + const generated = localExercises; + + const handleRemove = (index: number) => { + setLocalExercises((prev) => (prev ? prev.filter((_, i) => i !== index) : null)); + }; + + return ( + + ); +} diff --git a/src/components/ai/AiGradeExplainer.tsx b/src/components/ai/AiGradeExplainer.tsx new file mode 100644 index 0000000..8fbb677 --- /dev/null +++ b/src/components/ai/AiGradeExplainer.tsx @@ -0,0 +1,78 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Sparkles, Loader2 } from "lucide-react"; +import { coachingService } from "@/services/coaching.service"; +import { useToast } from "@/hooks/use-toast"; + +export default function AiGradeExplainer({ + studentName, + scores, +}: { + studentName: string; + scores?: Record ; +}) { + const [open, setOpen] = useState(false); + const { toast } = useToast(); + + const explainMutation = useMutation({ + mutationFn: () => + coachingService.explain({ + context: `IELTS / course grades for student: ${studentName}. Summarize what the scores mean and what to focus on next.`, + scores, + }), + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Could not explain grades", + description: err.message || "Try again in a moment.", + }); + }, + }); + + const handleOpen = () => { + setOpen(true); + explainMutation.reset(); + explainMutation.mutate(); + }; + + return ( + <> + + + > + ); +} diff --git a/src/components/ai/AiGradingAssistant.tsx b/src/components/ai/AiGradingAssistant.tsx new file mode 100644 index 0000000..f440d5a --- /dev/null +++ b/src/components/ai/AiGradingAssistant.tsx @@ -0,0 +1,100 @@ +import { useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Sparkles, Loader2 } from "lucide-react"; +import { analyticsService } from "@/services/analytics.service"; +import { useToast } from "@/hooks/use-toast"; + +interface Props { + onAccept: (marks: number, feedback: string) => void; + submissionId?: number; + submissionText?: string; + rubricId?: number; +} + +const DEFAULT_TEXT = + "Sample submission for AI grading suggestion. Replace by passing submissionText when integrating with real submissions."; + +export default function AiGradingAssistant({ + onAccept, + submissionId = 1, + submissionText = DEFAULT_TEXT, + rubricId, +}: Props) { + const { toast } = useToast(); + + const gradeMutation = useMutation({ + mutationFn: () => + analyticsService.getGradingSuggestion({ + submission_id: submissionId, + text: submissionText, + ...(rubricId !== undefined ? { rubric_id: rubricId } : {}), + }), + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Could not load AI grade", + description: err.message || "Try again later.", + }); + }, + }); + + useEffect(() => { + gradeMutation.mutate(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- refetch when inputs change + }, [submissionId, submissionText, rubricId]); + + const data = gradeMutation.data; + const marks = data ? Math.round(data.overall_score) : 0; + const feedbackBlock = data + ? [ + data.feedback, + data.suggestions?.length + ? `Suggestions:\n${data.suggestions.map((s) => `• ${s}`).join("\n")}` + : "", + ] + .filter(Boolean) + .join("\n\n") + : ""; + + return ( + ++ ); +} diff --git a/src/components/ai/AiInsightsPanel.tsx b/src/components/ai/AiInsightsPanel.tsx new file mode 100644 index 0000000..1916ef9 --- /dev/null +++ b/src/components/ai/AiInsightsPanel.tsx @@ -0,0 +1,105 @@ +import { useEffect, useMemo } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Sparkles, TrendingUp, AlertTriangle, Trophy, Loader2 } from "lucide-react"; +import { analyticsService } from "@/services/analytics.service"; +import type { AiInsight } from "@/types"; +import { useToast } from "@/hooks/use-toast"; + +const EMPTY_PAYLOAD: Record+
+ {gradeMutation.isPending ? ( +AI Suggested Grade + ++ ) : gradeMutation.isError ? ( +Analyzing submission... + Could not load a suggestion. Check the console or try again.
+ ) : data ? ( + <> +++Suggested marks
+{marks}/100
+++ {data.scores && Object.keys(data.scores).length > 0 && ( +Suggested feedback
+{feedbackBlock}
+++ )} + + > + ) : null} +Rubric scores
++ {Object.entries(data.scores).map(([k, v]) => ( +
+- + {k}: {v} +
+ ))} += {}; + +function insightIcon(type: AiInsight["type"]) { + switch (type) { + case "positive": + return Trophy; + case "warning": + return AlertTriangle; + default: + return TrendingUp; + } +} + +function insightColor(type: AiInsight["type"]) { + switch (type) { + case "positive": + return "text-primary"; + case "warning": + return "text-warning"; + default: + return "text-success"; + } +} + +interface Props { + data?: Record ; +} + +export default function AiInsightsPanel({ data = EMPTY_PAYLOAD }: Props) { + const { toast } = useToast(); + const payloadKey = useMemo(() => JSON.stringify(data), [data]); + + const mutation = useMutation({ + mutationFn: (payload: Record ) => analyticsService.getInsights(payload), + onError: (err: Error) => { + toast({ + title: "Insights unavailable", + description: err.message || "Could not load AI insights.", + variant: "destructive", + }); + }, + }); + + useEffect(() => { + mutation.mutate(data); + // eslint-disable-next-line react-hooks/exhaustive-deps -- refetch when serialized payload changes + }, [payloadKey]); + + const items = mutation.data ?? []; + + return ( + + + ); +} diff --git a/src/components/ai/AiReportNarrative.tsx b/src/components/ai/AiReportNarrative.tsx new file mode 100644 index 0000000..977b2c9 --- /dev/null +++ b/src/components/ai/AiReportNarrative.tsx @@ -0,0 +1,56 @@ +import { useEffect, useMemo } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Sparkles, Loader2 } from "lucide-react"; +import { analyticsService } from "@/services/analytics.service"; +import { useToast } from "@/hooks/use-toast"; + +interface Props { + report_type: string; + data: Record+ ++ ++ AI Platform Insights + + {mutation.isPending && ( + +++ )} + {mutation.isError && !mutation.isPending && ( ++ Loading insights… + Could not load insights.
+ )} + {mutation.isSuccess && items.length === 0 && ( +No insights available for this view.
+ )} + {!mutation.isPending && items.length > 0 && ( ++ {items.map((item) => { + const Icon = insightIcon(item.type); + const color = insightColor(item.type); + return ( ++ )} +++ ); + })} ++++ {item.title} + {item.description}
+ {item.metric != null && item.value != null && ( ++ {item.metric}: {item.value} +
+ )} +; +} + +export default function AiReportNarrative({ report_type, data }: Props) { + const { toast } = useToast(); + const dataKey = useMemo(() => JSON.stringify(data), [data]); + + const mutation = useMutation({ + mutationFn: (vars: { report_type: string; data: Record }) => + analyticsService.getReportNarrative(vars), + onError: (err: Error) => { + toast({ + title: "Summary unavailable", + description: err.message || "Could not generate AI summary.", + variant: "destructive", + }); + }, + }); + + useEffect(() => { + mutation.mutate({ report_type, data }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [report_type, dataKey]); + + return ( + ++ ); +} diff --git a/src/components/ai/AiRiskBadge.tsx b/src/components/ai/AiRiskBadge.tsx new file mode 100644 index 0000000..afa602f --- /dev/null +++ b/src/components/ai/AiRiskBadge.tsx @@ -0,0 +1,45 @@ +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +export type AiRiskLevel = "At Risk" | "Monitor" | "On Track"; + +interface Props { + riskLevel?: AiRiskLevel; + reasons?: string[]; +} + +export default function AiRiskBadge({ riskLevel, reasons }: Props) { + if (!riskLevel) { + return ( ++ + AI Summary + {mutation.isPending && ( +++
+ )} + {mutation.isError && !mutation.isPending && ( ++ Generating summary… + Could not load summary.
+ )} + {!mutation.isPending && mutation.data?.narrative && ( +{mutation.data.narrative}
+ )} + {mutation.isSuccess && !mutation.data?.narrative?.trim() && ( +No summary returned.
+ )} ++ + ); + } + + const variant: "destructive" | "secondary" | "default" = + riskLevel === "At Risk" ? "destructive" : riskLevel === "Monitor" ? "secondary" : "default"; + + const tooltipText = + reasons?.filter(Boolean).length ? reasons!.filter(Boolean).join(" · ") : "No additional details provided."; + + return ( ++ ++ Unknown + ++ +Risk data not available from profile.
++ + ); +} diff --git a/src/components/ai/AiSearchBar.tsx b/src/components/ai/AiSearchBar.tsx new file mode 100644 index 0000000..a4d9f1b --- /dev/null +++ b/src/components/ai/AiSearchBar.tsx @@ -0,0 +1,103 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Input } from "@/components/ui/input"; +import { Sparkles, Search, Loader2, X } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { analyticsService } from "@/services/analytics.service"; +import { useToast } from "@/hooks/use-toast"; + +export default function AiSearchBar() { + const [query, setQuery] = useState(""); + const navigate = useNavigate(); + const { toast } = useToast(); + + const searchMutation = useMutation({ + mutationFn: (q: string) => analyticsService.search(q), + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Search failed", + description: err.message || "Could not complete AI search.", + }); + }, + }); + + const handleSearch = () => { + if (!query.trim() || searchMutation.isPending) return; + searchMutation.mutate(query.trim()); + }; + + const results = searchMutation.data; + + return ( ++ ++ {riskLevel} + ++ +{tooltipText}
+++ ); +} diff --git a/src/components/ai/AiStudyCoach.tsx b/src/components/ai/AiStudyCoach.tsx new file mode 100644 index 0000000..5b4f7cc --- /dev/null +++ b/src/components/ai/AiStudyCoach.tsx @@ -0,0 +1,102 @@ +import { useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Sparkles, RefreshCw, Loader2, Lightbulb } from "lucide-react"; +import { coachingService } from "@/services/coaching.service"; +import { useToast } from "@/hooks/use-toast"; + +export default function AiStudyCoach() { + const { toast } = useToast(); + + const suggestMutation = useMutation({ + mutationFn: () => coachingService.suggest(), + onError: (err: Error) => { + toast({ + variant: "destructive", + title: "Could not load coach tips", + description: err.message || "Try refreshing in a moment.", + }); + }, + }); + + useEffect(() => { + suggestMutation.mutate(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- load once on mount + }, []); + + const refresh = () => { + suggestMutation.mutate(); + }; + + const suggestions = suggestMutation.data?.suggestions ?? []; + const planTips = suggestMutation.data?.study_plan_tips ?? []; + + return ( +++ + {(searchMutation.isPending || results !== undefined) && ( ++ + { + setQuery(e.target.value); + searchMutation.reset(); + }} + onKeyDown={(e) => e.key === "Enter" && handleSearch()} + /> + {query && ( + + )} + + {searchMutation.isPending ? ( ++ )} +++ ) : results && results.length > 0 ? ( ++ AI is searching... + + {results.map((r, i) => ( ++ ) : ( +++ ))} ++ ++{r.title}
+{r.description}
+ {r.url && ( + + )} +++ )} ++ + No results for "{query}". Try a different question or keyword. +
++ + ); +} diff --git a/src/components/ai/AiTipBanner.tsx b/src/components/ai/AiTipBanner.tsx new file mode 100644 index 0000000..4e51ef3 --- /dev/null +++ b/src/components/ai/AiTipBanner.tsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Sparkles, X, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { coachingService } from "@/services/coaching.service"; + +interface Props { + context?: string; + variant?: "tip" | "insight" | "recommendation"; + dismissible?: boolean; +} + +export default function AiTipBanner({ context = "dashboard", variant = "tip", dismissible = true }: Props) { + const [dismissed, setDismissed] = useState(false); + + const { data, isLoading, isError, error } = useQuery({ + queryKey: ["ai", "tip", context], + queryFn: () => coachingService.getTip(context), + }); + + if (dismissed) return null; + + const bgClass = variant === "recommendation" ? "bg-accent/50 border-accent" : variant === "insight" ? "bg-primary/5 border-primary/20" : "bg-muted/50 border-muted-foreground/10"; + + if (isLoading) { + return ( ++ ++++ + ++ Your AI Study Coach + + {suggestMutation.isPending && !suggestMutation.data ? ( + +++ ) : ( + <> + {suggestions.length > 0 && ( +Analyzing your performance... + ++ )} + {planTips.length > 0 && ( ++
+Suggestions + + {suggestions.map((s, i) => ( +
+- {s}
+ ))} +++ )} + {!suggestMutation.isPending && + suggestions.length === 0 && + planTips.length === 0 && ( ++
+Study plan tips + + {planTips.map((tip, i) => ( ++++ ))} +{tip}
++ No suggestions yet. Try refreshing. +
+ )} + > + )} +++ ); + } + + if (isError || !data) { + return ( ++ Loading AI tip… + ++ ); + } + + if (!data.content?.trim() && !data.title?.trim()) { + return ( ++ + AI Tip ++ {dismissible && ( + + )} ++ {isError ? (error instanceof Error ? error.message : "Could not load tip.") : "No tip available."} +
+++ ); + } + + return ( ++ + AI {variant === "tip" ? "Tip" : variant === "insight" ? "Insight" : "Recommendation"} ++No tip for this context yet.
+++ ); +} diff --git a/src/components/ai/AiWritingHelper.tsx b/src/components/ai/AiWritingHelper.tsx new file mode 100644 index 0000000..0ed561f --- /dev/null +++ b/src/components/ai/AiWritingHelper.tsx @@ -0,0 +1,140 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { Sparkles, Loader2, PenLine, CheckCircle, BarChart3, ChevronDown } from "lucide-react"; +import { coachingService } from "@/services/coaching.service"; +import { useToast } from "@/hooks/use-toast"; + +type Mode = "improve" | "grammar" | "band" | null; + +interface Props { + text: string; + task_type?: string; +} + +export default function AiWritingHelper({ text, task_type = "ielts_writing" }: Props) { + const [open, setOpen] = useState(false); + const [activeMode, setActiveMode] = useState+ + + {data.title?.trim() || `AI ${variant === "tip" ? "Tip" : variant === "insight" ? "Insight" : "Recommendation"}`} + ++ {dismissible && ( + + )} +{data.content}
+(null); + const [showResult, setShowResult] = useState(false); + const { toast } = useToast(); + + const mutation = useMutation({ + mutationFn: (mode: NonNullable ) => + coachingService.writingHelp({ + text: text.trim(), + task_type: `${task_type}:${mode}`, + }), + onSuccess: () => setShowResult(true), + onError: (err: Error) => { + toast({ + title: "Writing help failed", + description: err.message || "Could not analyze your writing. Try again.", + variant: "destructive", + }); + }, + }); + + const handleAction = (mode: Mode) => { + if (!mode) return; + if (!text.trim()) { + toast({ + title: "Add some text first", + description: "Enter your draft in the text area so AI can analyze it.", + variant: "destructive", + }); + return; + } + setActiveMode(mode); + setShowResult(false); + mutation.mutate(mode); + }; + + const loading = mutation.isPending; + + return ( + + + ); +} diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..1e7878c --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,52 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef+ + ++ ++ + + ++ + {loading && ( +++ )} + + {showResult && !loading && mutation.data && activeMode === "improve" && ( +AI is analyzing your writing... + + {mutation.data.feedback && ( ++ )} + + {showResult && !loading && mutation.data && activeMode === "grammar" && ( +++ )} + {mutation.data.improved && ( ++
+Feedback + {mutation.data.feedback}
+++ )} ++
+Improved Version + {mutation.data.improved}
+++ )} + + {showResult && !loading && mutation.data && activeMode === "band" && ( ++
+ {(mutation.data.grammar_notes?.length ?? 0) > 0 ? ( + mutation.data.grammar_notes!.map((note, i) => ( +Grammar notes + ++ )) + ) : ( +{note}
+No grammar issues flagged.
+ )} + {mutation.data.feedback ? ( +{mutation.data.feedback}
+ ) : null} +++ )} ++
+Estimated band / assessment + {mutation.data.feedback}
+ {mutation.data.improved ? ( +{mutation.data.improved}
+ ) : null} +, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRefsvg]:rotate-180", + className, + )} + {...props} + > + {children} + ++ , + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + +)); + +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..6dfbfb4 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,104 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef{children}+, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes+ + ) => ( + +); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes ) => ( + +); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..2efc3c8 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( + +)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..c9e6f4b --- /dev/null +++ b/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +const AspectRatio = AspectRatioPrimitive.Root; + +export { AspectRatio }; diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..68d21bb --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,38 @@ +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef , + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..0853c44 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps extends React.HTMLAttributes , VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ; +} + +export { Badge, badgeVariants }; diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..ca91ff5 --- /dev/null +++ b/src/components/ui/breadcrumb.tsx @@ -0,0 +1,90 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) => ); +Breadcrumb.displayName = "Breadcrumb"; + +const BreadcrumbList = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +BreadcrumbList.displayName = "BreadcrumbList"; + +const BreadcrumbItem = React.forwardRef
>( + ({ className, ...props }, ref) => ( + + ), +); +BreadcrumbItem.displayName = "BreadcrumbItem"; + +const BreadcrumbLink = React.forwardRef< + HTMLAnchorElement, + React.ComponentPropsWithoutRef<"a"> & { + asChild?: boolean; + } +>(({ asChild, className, ...props }, ref) => { + const Comp = asChild ? Slot : "a"; + + return ; +}); +BreadcrumbLink.displayName = "BreadcrumbLink"; + +const BreadcrumbPage = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +BreadcrumbPage.displayName = "BreadcrumbPage"; + +const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => ( + +); +BreadcrumbSeparator.displayName = "BreadcrumbSeparator"; + +const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => ( + +); +BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"; + +export { + Breadcrumb, + BreadcrumbList, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbPage, + BreadcrumbSeparator, + BreadcrumbEllipsis, +}; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..cdedd4f --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes , + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef ( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 0000000..900a69e --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { DayPicker } from "react-day-picker"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +export type CalendarProps = React.ComponentProps ; + +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { + return ( + , + IconRight: ({ ..._props }) => , + }} + {...props} + /> + ); +} +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..e282748 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef >(({ className, ...props }, ref) => ( + +)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef >( + ({ className, ...props }, ref) => , +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef >( + ({ className, ...props }, ref) => ( + + ), +); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx new file mode 100644 index 0000000..3aa0f31 --- /dev/null +++ b/src/components/ui/carousel.tsx @@ -0,0 +1,224 @@ +import * as React from "react"; +import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters ; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; +}; + +type CarouselContextProps = { + carouselRef: ReturnType [0]; + api: ReturnType [1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext (null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a "); + } + + return context; +} + +const Carousel = React.forwardRef & CarouselProps>( + ({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins, + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent ) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrev(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext], + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api?.off("select", onSelect); + }; + }, [api, onSelect]); + + return ( + + + ); + }, +); +Carousel.displayName = "Carousel"; + +const CarouselContent = React.forwardRef+ {children} ++>( + ({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( + + ++ ); + }, +); +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = React.forwardRef>( + ({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( + + ); + }, +); +CarouselItem.displayName = "CarouselItem"; + +const CarouselPrevious = React.forwardRef >( + ({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); + }, +); +CarouselPrevious.displayName = "CarouselPrevious"; + +const CarouselNext = React.forwardRef >( + ({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); + }, +); +CarouselNext.displayName = "CarouselNext"; + +export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }; diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx new file mode 100644 index 0000000..08d40d9 --- /dev/null +++ b/src/components/ui/chart.tsx @@ -0,0 +1,303 @@ +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; + +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ({ color?: string; theme?: never } | { color?: never; theme: Record }); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext (null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps ["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + + + ); +}); +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color); + + if (!colorConfig.length) { + return null; + } + + return ( ++++ {children} +