diff --git a/README.md b/README.md index 2d1d617..a89ebe4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Modern database system for legal practice management, financial tracking, and do ## 🛠️ Technology Stack - **Backend**: Python 3.12, FastAPI, SQLAlchemy 2.0+ - **Database**: SQLite (single file) -- **Frontend**: Jinja2 templates, Bootstrap 5.3, vanilla JavaScript +- **Frontend**: Jinja2 templates, Tailwind CSS 3, vanilla JavaScript - **Authentication**: JWT with bcrypt password hashing - **Validation**: Pydantic v2 diff --git a/app/api/auth.py b/app/api/auth.py index 7ada190..4a65234 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -20,7 +20,8 @@ from app.auth.schemas import ( Token, UserCreate, UserResponse, - LoginRequest + LoginRequest, + ThemePreferenceUpdate ) from app.config import settings @@ -116,4 +117,23 @@ async def list_users( ): """List all users (admin only)""" users = db.query(User).all() - return users \ No newline at end of file + return users + + +@router.post("/theme-preference") +async def update_theme_preference( + theme_data: ThemePreferenceUpdate, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Update user's theme preference""" + if theme_data.theme_preference not in ['light', 'dark']: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Theme preference must be 'light' or 'dark'" + ) + + current_user.theme_preference = theme_data.theme_preference + db.commit() + + return {"message": "Theme preference updated successfully", "theme": theme_data.theme_preference} \ No newline at end of file diff --git a/app/api/documents.py b/app/api/documents.py index 6b8cf6f..7b7203c 100644 --- a/app/api/documents.py +++ b/app/api/documents.py @@ -2,7 +2,7 @@ Document Management API endpoints - QDROs, Templates, and General Documents """ from typing import List, Optional, Dict, Any -from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File +from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File, Form from sqlalchemy.orm import Session, joinedload from sqlalchemy import or_, func, and_, desc, asc, text from datetime import date, datetime @@ -17,6 +17,7 @@ from app.models.rolodex import Rolodex from app.models.lookups import FormIndex, FormList, Footer, Employee from app.models.user import User from app.auth.security import get_current_user +from app.models.additional import Document router = APIRouter() @@ -662,4 +663,105 @@ def _merge_template_variables(content: str, variables: Dict[str, Any]) -> str: merged = merged.replace(f"{{{{{var_name}}}}}", str(value or "")) merged = merged.replace(f"^{var_name}", str(value or "")) - return merged \ No newline at end of file + return merged + + +@router.post("/upload/{file_no}") +async def upload_document( + file_no: str, + file: UploadFile = File(...), + description: Optional[str] = Form(None), + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Upload a document to a file""" + file_obj = db.query(FileModel).filter(FileModel.file_no == file_no).first() + if not file_obj: + raise HTTPException(status_code=404, detail="File not found") + + if not file.filename: + raise HTTPException(status_code=400, detail="No file uploaded") + + allowed_types = [ + "application/pdf", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "image/jpeg", + "image/png" + ] + if file.content_type not in allowed_types: + raise HTTPException(status_code=400, detail="Invalid file type") + + max_size = 10 * 1024 * 1024 # 10MB + content = await file.read() + if len(content) > max_size: + raise HTTPException(status_code=400, detail="File too large") + + upload_dir = f"uploads/{file_no}" + os.makedirs(upload_dir, exist_ok=True) + + ext = file.filename.split(".")[-1] + unique_name = f"{uuid.uuid4()}.{ext}" + path = f"{upload_dir}/{unique_name}" + + with open(path, "wb") as f: + f.write(content) + + doc = Document( + file_no=file_no, + filename=file.filename, + path=path, + description=description, + type=file.content_type, + size=len(content), + uploaded_by=current_user.username + ) + db.add(doc) + db.commit() + db.refresh(doc) + return doc + +@router.get("/{file_no}/uploaded") +async def list_uploaded_documents( + file_no: str, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """List uploaded documents for a file""" + docs = db.query(Document).filter(Document.file_no == file_no).all() + return docs + +@router.delete("/uploaded/{doc_id}") +async def delete_document( + doc_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Delete an uploaded document""" + doc = db.query(Document).filter(Document.id == doc_id).first() + if not doc: + raise HTTPException(status_code=404, detail="Document not found") + + if os.path.exists(doc.path): + os.remove(doc.path) + + db.delete(doc) + db.commit() + return {"message": "Document deleted successfully"} + +@router.put("/uploaded/{doc_id}") +async def update_document( + doc_id: int, + description: str = Form(...), + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """Update document description""" + doc = db.query(Document).filter(Document.id == doc_id).first() + if not doc: + raise HTTPException(status_code=404, detail="Document not found") + + doc.description = description + db.commit() + db.refresh(doc) + return doc \ No newline at end of file diff --git a/app/api/settings.py b/app/api/settings.py new file mode 100644 index 0000000..7a05e81 --- /dev/null +++ b/app/api/settings.py @@ -0,0 +1,37 @@ +""" +Public (authenticated) settings endpoints for client configuration +""" +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session + +from app.database.base import get_db +from app.auth.security import get_current_user +from app.models.user import User +from app.models.lookups import SystemSetup + + +router = APIRouter() + + +@router.get("/inactivity_warning_minutes") +async def get_inactivity_warning_minutes( + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user), +): + """Returns the inactivity warning threshold in minutes (default 240).""" + default_minutes = 240 + setting = ( + db.query(SystemSetup) + .filter(SystemSetup.setting_key == "inactivity_warning_minutes") + .first() + ) + if not setting: + return {"minutes": default_minutes} + + try: + minutes = int(setting.setting_value) + except Exception: + minutes = default_minutes + return {"minutes": minutes} + + diff --git a/app/auth/schemas.py b/app/auth/schemas.py index 4c8f0ef..880d146 100644 --- a/app/auth/schemas.py +++ b/app/auth/schemas.py @@ -30,11 +30,17 @@ class UserResponse(UserBase): id: int is_active: bool is_admin: bool + theme_preference: Optional[str] = "light" class Config: from_attributes = True +class ThemePreferenceUpdate(BaseModel): + """Theme preference update schema""" + theme_preference: str + + class Token(BaseModel): """Token response schema""" access_token: str diff --git a/app/main.py b/app/main.py index 0b168b7..6fc410d 100644 --- a/app/main.py +++ b/app/main.py @@ -34,6 +34,7 @@ app.add_middleware( # Mount static files app.mount("/static", StaticFiles(directory="static"), name="static") +app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads") # Templates templates = Jinja2Templates(directory="templates") @@ -48,6 +49,7 @@ from app.api.search import router as search_router from app.api.admin import router as admin_router from app.api.import_data import router as import_router from app.api.support import router as support_router +from app.api.settings import router as settings_router app.include_router(auth_router, prefix="/api/auth", tags=["authentication"]) app.include_router(customers_router, prefix="/api/customers", tags=["customers"]) @@ -58,6 +60,7 @@ app.include_router(search_router, prefix="/api/search", tags=["search"]) app.include_router(admin_router, prefix="/api/admin", tags=["admin"]) app.include_router(import_router, prefix="/api/import", tags=["import"]) app.include_router(support_router, prefix="/api/support", tags=["support"]) +app.include_router(settings_router, prefix="/api/settings", tags=["settings"]) @app.get("/", response_class=HTMLResponse) diff --git a/app/models/__init__.py b/app/models/__init__.py index df6835b..108063c 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -8,7 +8,7 @@ from .files import File from .ledger import Ledger from .qdro import QDRO from .audit import AuditLog, LoginAttempt -from .additional import Deposit, Payment, FileNote, FormVariable, ReportVariable +from .additional import Deposit, Payment, FileNote, FormVariable, ReportVariable, Document from .support import SupportTicket, TicketResponse, TicketStatus, TicketPriority, TicketCategory from .pensions import ( Pension, PensionSchedule, MarriageHistory, DeathBenefit, @@ -23,7 +23,7 @@ from .lookups import ( __all__ = [ "BaseModel", "User", "Rolodex", "Phone", "File", "Ledger", "QDRO", "AuditLog", "LoginAttempt", - "Deposit", "Payment", "FileNote", "FormVariable", "ReportVariable", + "Deposit", "Payment", "FileNote", "FormVariable", "ReportVariable", "Document", "SupportTicket", "TicketResponse", "TicketStatus", "TicketPriority", "TicketCategory", "Pension", "PensionSchedule", "MarriageHistory", "DeathBenefit", "SeparationAgreement", "LifeTable", "NumberTable", diff --git a/app/models/additional.py b/app/models/additional.py index d1ef124..b6d4a6b 100644 --- a/app/models/additional.py +++ b/app/models/additional.py @@ -1,7 +1,7 @@ """ Additional models for complete legacy system coverage """ -from sqlalchemy import Column, Integer, String, Text, Date, Float, ForeignKey +from sqlalchemy import Column, Integer, String, Text, Date, Float, ForeignKey, func, DateTime from sqlalchemy.orm import relationship from app.models.base import BaseModel @@ -95,4 +95,25 @@ class ReportVariable(BaseModel): active = Column(Integer, default=1) # Legacy system uses integer for boolean def __repr__(self): - return f"" \ No newline at end of file + return f"" + + +class Document(BaseModel): + __tablename__ = "documents" + + id = Column(Integer, primary_key=True, index=True) + file_no = Column(String(45), ForeignKey("files.file_no"), nullable=False, index=True) + filename = Column(String(255), nullable=False) + path = Column(String(512), nullable=False) + description = Column(Text) + type = Column(String(50)) + size = Column(Integer) + uploaded_by = Column(String, ForeignKey("users.username")) + upload_date = Column(DateTime, default=func.now()) + + # Relationships + file = relationship("File", back_populates="documents") + user = relationship("User") + + def __repr__(self): + return f"" \ No newline at end of file diff --git a/app/models/files.py b/app/models/files.py index 7b45967..794f44a 100644 --- a/app/models/files.py +++ b/app/models/files.py @@ -64,4 +64,5 @@ class File(BaseModel): death_benefits = relationship("DeathBenefit", back_populates="file", cascade="all, delete-orphan") separation_agreements = relationship("SeparationAgreement", back_populates="file", cascade="all, delete-orphan") payments = relationship("Payment", back_populates="file", cascade="all, delete-orphan") - notes = relationship("FileNote", back_populates="file", cascade="all, delete-orphan") \ No newline at end of file + notes = relationship("FileNote", back_populates="file", cascade="all, delete-orphan") + documents = relationship("Document", back_populates="file", cascade="all, delete-orphan") \ No newline at end of file diff --git a/app/models/user.py b/app/models/user.py index 2f9c93b..e7ef8d7 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -25,6 +25,9 @@ class User(BaseModel): is_active = Column(Boolean, default=True) is_admin = Column(Boolean, default=False) + # User Preferences + theme_preference = Column(String(10), default='light') # 'light', 'dark' + # Activity tracking last_login = Column(DateTime(timezone=True)) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5930a26 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1517 @@ +{ + "name": "delphi-database", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "delphi-database", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@tailwindcss/forms": "^0.5.10", + "tailwindcss": "^3.4.10" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", + "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c6f3dfb --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "delphi-database", + "version": "1.0.0", + "description": "A modern Python web application built with FastAPI to replace the legacy Pascal-based database system. This system maintains the familiar keyboard shortcuts and workflows while providing a robust, modular backend with a clean web interface.", + "main": "tailwind.config.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/HotSwapp/delphi-database.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/HotSwapp/delphi-database/issues" + }, + "homepage": "https://github.com/HotSwapp/delphi-database#readme", + "devDependencies": { + "@tailwindcss/forms": "^0.5.10", + "tailwindcss": "^3.4.10" + } +} diff --git a/static/css/components.css b/static/css/components.css deleted file mode 100644 index d0be568..0000000 --- a/static/css/components.css +++ /dev/null @@ -1,259 +0,0 @@ -/* Delphi Database System - Component Styles */ - -/* Login Component */ -.login-page { - background-color: #f8f9fa; -} - -.login-card { - max-width: 400px; - margin: 2rem auto; -} - -.login-logo { - height: 60px; - margin-bottom: 1rem; -} - -.login-form .input-group-text { - background-color: #e9ecef; - border-right: none; -} - -.login-form .form-control { - border-left: none; -} - -.login-form .form-control:focus { - border-left: none; - box-shadow: none; -} - -.login-status { - margin-top: 1rem; -} - -/* Customer Management Component */ -.customer-search-panel { - background-color: white; - border-radius: 0.5rem; - padding: 1.5rem; - margin-bottom: 1.5rem; - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); -} - -.customer-table-container { - background-color: white; - border-radius: 0.5rem; - overflow: hidden; -} - -.customer-modal .modal-dialog { - max-width: 90%; -} - -.customer-form-section { - margin-bottom: 1.5rem; -} - -.customer-form-section .card-header { - background-color: #f8f9fa; - border-bottom: 1px solid #dee2e6; - font-weight: 600; -} - -.phone-entry { - background-color: #f8f9fa; - padding: 0.75rem; - border-radius: 0.375rem; - margin-bottom: 0.5rem; -} - -.phone-entry:last-child { - margin-bottom: 0; -} - -/* Statistics Modal */ -.stats-modal .modal-body { - background-color: #f8f9fa; -} - -.stats-section { - background-color: white; - border-radius: 0.375rem; - padding: 1rem; - margin-bottom: 1rem; -} - -/* Navigation Component */ -.navbar-shortcuts small { - font-size: 0.7rem; - opacity: 0.8; -} - -.keyboard-shortcuts-modal .modal-body { - background-color: #f8f9fa; -} - -.shortcuts-section { - background-color: white; - border-radius: 0.375rem; - padding: 1rem; - margin-bottom: 1rem; -} - -/* Dashboard Component */ -.dashboard-card { - transition: transform 0.2s ease-in-out; -} - -.dashboard-card:hover { - transform: translateY(-2px); -} - -.dashboard-stats { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-radius: 1rem; -} - -.recent-activity { - max-height: 400px; - overflow-y: auto; -} - -.activity-item { - border-left: 3px solid var(--delphi-primary); - padding-left: 1rem; - margin-bottom: 1rem; -} - -.activity-item:last-child { - margin-bottom: 0; -} - -/* Form Components */ -.form-floating-custom .form-control { - height: calc(3.5rem + 2px); - line-height: 1.25; -} - -.form-floating-custom .form-control::placeholder { - color: transparent; -} - -.form-floating-custom .form-control:focus ~ .form-label, -.form-floating-custom .form-control:not(:placeholder-shown) ~ .form-label { - opacity: 0.65; - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} - -/* Search Components */ -.search-results { - position: absolute; - top: 100%; - left: 0; - right: 0; - background-color: white; - border: 1px solid #dee2e6; - border-top: none; - border-radius: 0 0 0.375rem 0.375rem; - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); - z-index: 1000; - max-height: 300px; - overflow-y: auto; -} - -.search-result-item { - padding: 0.75rem 1rem; - border-bottom: 1px solid #f8f9fa; - cursor: pointer; - transition: background-color 0.15s ease-in-out; -} - -.search-result-item:hover { - background-color: #f8f9fa; -} - -.search-result-item:last-child { - border-bottom: none; -} - -/* Notification Components */ -#notification-container, -.notification-container { - z-index: 1070 !important; -} - -#notification-container { - position: fixed; - top: 1rem; - right: 1rem; - width: 300px; -} - -.notification { - margin-bottom: 0.5rem; - animation: slideInRight 0.3s ease-out; -} - -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(100%); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -/* Loading Components */ -.loading-overlay { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.8); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.loading-spinner { - width: 2rem; - height: 2rem; - border: 0.25rem solid #f3f3f3; - border-top: 0.25rem solid var(--delphi-primary); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -/* Table Components */ -.sortable-header { - cursor: pointer; - user-select: none; -} - -.sortable-header:hover { - background-color: rgba(255, 255, 255, 0.1); -} - -.sortable-header.sort-asc::after { - content: " ↑"; -} - -.sortable-header.sort-desc::after { - content: " ↓"; -} - -/* Form Validation */ -.invalid-feedback.hidden { - display: none; -} - -.invalid-feedback.visible { - display: block; -} \ No newline at end of file diff --git a/static/css/input.css b/static/css/input.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/static/css/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/static/css/main.css b/static/css/main.css deleted file mode 100644 index dcb50ed..0000000 --- a/static/css/main.css +++ /dev/null @@ -1,236 +0,0 @@ -/* Delphi Consulting Group Database System - Main Styles */ - -/* Variables */ -:root { - --delphi-primary: #0d6efd; - --delphi-secondary: #6c757d; - --delphi-success: #198754; - --delphi-info: #0dcaf0; - --delphi-warning: #ffc107; - --delphi-danger: #dc3545; - --delphi-dark: #212529; -} - -/* Body and base styles */ -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; - background-color: #f8f9fa; -} - -/* Navigation customizations */ -.navbar-brand img { - filter: brightness(0) invert(1); -} - -/* Card customizations */ -.card { - border: none; - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); - transition: box-shadow 0.15s ease-in-out; -} - -.card:hover { - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); -} - -/* Button customizations */ -.btn { - border-radius: 0.375rem; - font-weight: 500; -} - -.btn-lg small { - font-size: 0.75rem; - font-weight: 400; -} - -/* Form customizations */ -.form-control { - border-radius: 0.375rem; -} - -.form-control:focus { - border-color: var(--delphi-primary); - box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); -} - -/* Table customizations */ -.table { - background-color: white; -} - -.table th { - border-top: none; - background-color: var(--delphi-primary); - color: white; - font-weight: 600; -} - -.table tbody tr:hover { - background-color: rgba(13, 110, 253, 0.05); -} - -/* Keyboard shortcut styling */ -kbd { - background-color: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 0.25rem; - color: #495057; - font-size: 0.8rem; - padding: 0.125rem 0.25rem; -} - -.nav-link small { - opacity: 0.7; - font-size: 0.7rem; -} - -/* Modal customizations */ -.modal-content { - border: none; - box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175); -} - -.modal-header { - background-color: var(--delphi-primary); - color: white; -} - -.modal-header .btn-close { - filter: invert(1); -} - -/* Status badges */ -.badge { - font-size: 0.8em; - font-weight: 500; -} - -/* Utility classes */ -.text-primary { color: var(--delphi-primary) !important; } -.text-secondary { color: var(--delphi-secondary) !important; } -.text-success { color: var(--delphi-success) !important; } -.text-info { color: var(--delphi-info) !important; } -.text-warning { color: var(--delphi-warning) !important; } -.text-danger { color: var(--delphi-danger) !important; } - -.bg-primary { background-color: var(--delphi-primary) !important; } -.bg-secondary { background-color: var(--delphi-secondary) !important; } -.bg-success { background-color: var(--delphi-success) !important; } -.bg-info { background-color: var(--delphi-info) !important; } -.bg-warning { background-color: var(--delphi-warning) !important; } -.bg-danger { background-color: var(--delphi-danger) !important; } - -/* Animation classes */ -.fade-in { - animation: fadeIn 0.3s ease-in; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .container-fluid { - padding-left: 1rem; - padding-right: 1rem; - } - - .nav-link small { - display: none; - } - - .btn-lg small { - display: none; - } -} - -/* Loading spinner */ -.spinner { - display: inline-block; - width: 1rem; - height: 1rem; - border: 2px solid #f3f3f3; - border-top: 2px solid var(--delphi-primary); - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Error and success messages */ -.alert { - border: none; - border-radius: 0.5rem; -} - -.alert-dismissible .btn-close { - padding: 1rem 0.75rem; -} - -/* Data tables */ -.table-responsive { - border-radius: 0.5rem; - overflow: hidden; -} - -/* Form sections */ -.form-section { - background: white; - border-radius: 0.5rem; - padding: 1.5rem; - margin-bottom: 1.5rem; - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); -} - -.form-section h5 { - color: var(--delphi-primary); - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid #e9ecef; -} - -/* Pagination */ -.pagination { - margin-bottom: 0; -} - -.page-link { - color: var(--delphi-primary); -} - -.page-item.active .page-link { - background-color: var(--delphi-primary); - border-color: var(--delphi-primary); -} - -/* Visibility utility classes */ -.hidden { - display: none !important; -} - -.visible { - display: block !important; -} - -.visible-inline { - display: inline !important; -} - -.visible-inline-block { - display: inline-block !important; -} - -/* Customer management specific styles */ -.delete-customer-btn { - display: none; -} - -.delete-customer-btn.show { - display: inline-block; -} \ No newline at end of file diff --git a/static/css/tailwind.css b/static/css/tailwind.css new file mode 100644 index 0000000..f99786b --- /dev/null +++ b/static/css/tailwind.css @@ -0,0 +1,2527 @@ +/* +! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; + text-align: inherit; +} + +::-webkit-datetime-edit { + display: inline-flex; +} + +::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { + padding-top: 0; + padding-bottom: 0; +} + +select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +[multiple],[size]:where(select:not([size="1"])) { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='checkbox']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); +} + +@media (forced-colors: active) { + [type='radio']:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +@media (forced-colors: active) { + [type='checkbox']:indeterminate { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px solid ButtonText; + outline: 1px auto -webkit-focus-ring-color; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.\!container { + width: 100% !important; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .\!container { + max-width: 640px !important; + } + + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .\!container { + max-width: 768px !important; + } + + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; + } + + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .\!container { + max-width: 1280px !important; + } + + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .\!container { + max-width: 1536px !important; + } + + .container { + max-width: 1536px; + } +} + +.form-input,.form-textarea,.form-select,.form-multiselect { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +.form-input:focus, .form-textarea:focus, .form-select:focus, .form-multiselect:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +.form-select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +.form-select:where([size]:not([size="1"])) { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + print-color-adjust: unset; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.pointer-events-none { + pointer-events: none; +} + +.visible { + visibility: visible; +} + +.collapse { + visibility: collapse; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.inset-0 { + inset: 0px; +} + +.inset-y-0 { + top: 0px; + bottom: 0px; +} + +.bottom-0 { + bottom: 0px; +} + +.end-0 { + inset-inline-end: 0px; +} + +.left-0 { + left: 0px; +} + +.left-3 { + left: 0.75rem; +} + +.right-0 { + right: 0px; +} + +.right-2 { + right: 0.5rem; +} + +.right-4 { + right: 1rem; +} + +.top-0 { + top: 0px; +} + +.top-1\/2 { + top: 50%; +} + +.top-4 { + top: 1rem; +} + +.z-10 { + z-index: 10; +} + +.z-50 { + z-index: 50; +} + +.col-span-1 { + grid-column: span 1 / span 1; +} + +.col-span-2 { + grid-column: span 2 / span 2; +} + +.float-end { + float: inline-end; +} + +.m-3 { + margin: 0.75rem; +} + +.m-auto { + margin: auto; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.mb-0 { + margin-bottom: 0px; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.me-2 { + margin-inline-end: 0.5rem; +} + +.me-3 { + margin-inline-end: 0.75rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.ml-4 { + margin-left: 1rem; +} + +.ml-auto { + margin-left: auto; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.ms-1 { + margin-inline-start: 0.25rem; +} + +.ms-2 { + margin-inline-start: 0.5rem; +} + +.mt-0\.5 { + margin-top: 0.125rem; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.mt-auto { + margin-top: auto; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.\!table { + display: table !important; +} + +.table { + display: table; +} + +.table-row { + display: table-row; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-10 { + height: 2.5rem; +} + +.h-12 { + height: 3rem; +} + +.h-16 { + height: 4rem; +} + +.h-4 { + height: 1rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-8 { + height: 2rem; +} + +.max-h-96 { + max-height: 24rem; +} + +.max-h-screen { + max-height: 100vh; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-10 { + width: 2.5rem; +} + +.w-16 { + width: 4rem; +} + +.w-4 { + width: 1rem; +} + +.w-48 { + width: 12rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-8 { + width: 2rem; +} + +.w-auto { + width: auto; +} + +.w-full { + width: 100%; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-6xl { + max-width: 72rem; +} + +.max-w-7xl { + max-width: 80rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-sm { + max-width: 24rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.flex-grow { + flex-grow: 1; +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-x-full { + --tw-translate-x: 100%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.resize-none { + resize: none; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.flex-col { + flex-direction: column; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-3 { + gap: 0.75rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.gap-8 { + gap: 2rem; +} + +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); +} + +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-neutral-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(226 232 240 / var(--tw-divide-opacity)); +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.border { + border-width: 1px; +} + +.border-2 { + border-width: 2px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-l-4 { + border-left-width: 4px; +} + +.border-t { + border-top-width: 1px; +} + +.border-blue-400 { + --tw-border-opacity: 1; + border-color: rgb(96 165 250 / var(--tw-border-opacity)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-green-400 { + --tw-border-opacity: 1; + border-color: rgb(74 222 128 / var(--tw-border-opacity)); +} + +.border-green-500 { + --tw-border-opacity: 1; + border-color: rgb(34 197 94 / var(--tw-border-opacity)); +} + +.border-neutral-200 { + --tw-border-opacity: 1; + border-color: rgb(226 232 240 / var(--tw-border-opacity)); +} + +.border-neutral-300 { + --tw-border-opacity: 1; + border-color: rgb(203 213 225 / var(--tw-border-opacity)); +} + +.border-primary-700 { + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity)); +} + +.border-red-400 { + --tw-border-opacity: 1; + border-color: rgb(248 113 113 / var(--tw-border-opacity)); +} + +.border-transparent { + border-color: transparent; +} + +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.border-yellow-400 { + --tw-border-opacity: 1; + border-color: rgb(250 204 21 / var(--tw-border-opacity)); +} + +.border-yellow-500 { + --tw-border-opacity: 1; + border-color: rgb(234 179 8 / var(--tw-border-opacity)); +} + +.border-t-transparent { + border-top-color: transparent; +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + +.bg-danger-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-gray-300 { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); +} + +.bg-gray-600 { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +.bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); +} + +.bg-info-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 249 255 / var(--tw-bg-opacity)); +} + +.bg-info-600 { + --tw-bg-opacity: 1; + background-color: rgb(8 145 178 / var(--tw-bg-opacity)); +} + +.bg-neutral-100 { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + +.bg-neutral-50 { + --tw-bg-opacity: 1; + background-color: rgb(248 250 252 / var(--tw-bg-opacity)); +} + +.bg-primary-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + +.bg-primary-600 { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity)); +} + +.bg-primary-700 { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} + +.bg-success-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity)); +} + +.bg-success-600 { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.bg-success-700 { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + +.bg-warning-600 { + --tw-bg-opacity: 1; + background-color: rgb(217 119 6 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-yellow-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 249 195 / var(--tw-bg-opacity)); +} + +.bg-yellow-500 { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity)); +} + +.bg-opacity-50 { + --tw-bg-opacity: 0.5; +} + +.fill-current { + fill: currentColor; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.px-1\.5 { + padding-left: 0.375rem; + padding-right: 0.375rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-2\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-1\.5 { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} + +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pl-7 { + padding-left: 1.75rem; +} + +.pr-3 { + padding-right: 0.75rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-end { + text-align: end; +} + +.font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.text-blue-700 { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.text-danger-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); +} + +.text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.text-green-500 { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); +} + +.text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity)); +} + +.text-info-600 { + --tw-text-opacity: 1; + color: rgb(8 145 178 / var(--tw-text-opacity)); +} + +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); +} + +.text-neutral-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-neutral-600 { + --tw-text-opacity: 1; + color: rgb(71 85 105 / var(--tw-text-opacity)); +} + +.text-neutral-700 { + --tw-text-opacity: 1; + color: rgb(51 65 85 / var(--tw-text-opacity)); +} + +.text-neutral-800 { + --tw-text-opacity: 1; + color: rgb(30 41 59 / var(--tw-text-opacity)); +} + +.text-neutral-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.text-primary-100 { + --tw-text-opacity: 1; + color: rgb(219 234 254 / var(--tw-text-opacity)); +} + +.text-primary-200 { + --tw-text-opacity: 1; + color: rgb(191 219 254 / var(--tw-text-opacity)); +} + +.text-primary-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity)); +} + +.text-success-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.text-warning-600 { + --tw-text-opacity: 1; + color: rgb(217 119 6 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-yellow-500 { + --tw-text-opacity: 1; + color: rgb(234 179 8 / var(--tw-text-opacity)); +} + +.text-yellow-700 { + --tw-text-opacity: 1; + color: rgb(161 98 7 / var(--tw-text-opacity)); +} + +.underline { + text-decoration-line: underline; +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.placeholder-gray-500::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(107 114 128 / var(--tw-placeholder-opacity)); +} + +.placeholder-gray-500::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(107 114 128 / var(--tw-placeholder-opacity)); +} + +.placeholder-neutral-400::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(148 163 184 / var(--tw-placeholder-opacity)); +} + +.placeholder-neutral-400::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(148 163 184 / var(--tw-placeholder-opacity)); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-soft { + --tw-shadow: 0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04); + --tw-shadow-colored: 0 2px 15px -3px var(--tw-shadow-color), 0 10px 20px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.blur { + --tw-blur: blur(8px); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-150 { + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.hover\:border-blue-500:hover { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.hover\:bg-danger-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.hover\:bg-info-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(14 116 144 / var(--tw-bg-opacity)); +} + +.hover\:bg-neutral-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + +.hover\:bg-neutral-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.hover\:bg-neutral-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(248 250 252 / var(--tw-bg-opacity)); +} + +.hover\:bg-primary-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.hover\:bg-primary-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + +.hover\:bg-success-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + +.hover\:bg-warning-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(180 83 9 / var(--tw-bg-opacity)); +} + +.hover\:bg-yellow-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + +.hover\:text-blue-500:hover { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.hover\:text-neutral-600:hover { + --tw-text-opacity: 1; + color: rgb(71 85 105 / var(--tw-text-opacity)); +} + +.hover\:text-primary-100:hover { + --tw-text-opacity: 1; + color: rgb(219 234 254 / var(--tw-text-opacity)); +} + +.hover\:text-primary-500:hover { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity)); +} + +.hover\:text-primary-600:hover { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity)); +} + +.hover\:text-primary-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:text-yellow-600:hover { + --tw-text-opacity: 1; + color: rgb(202 138 4 / var(--tw-text-opacity)); +} + +.focus\:z-10:focus { + z-index: 10; +} + +.focus\:border-primary-500:focus { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); +} + +.focus\:border-transparent:focus { + border-color: transparent; +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-primary-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); +} + +.focus\:ring-warning-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(245 158 11 / var(--tw-ring-opacity)); +} + +.focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} + +.dark\:block:is(.dark *) { + display: block; +} + +.dark\:hidden:is(.dark *) { + display: none; +} + +.dark\:divide-neutral-700:is(.dark *) > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(51 65 85 / var(--tw-divide-opacity)); +} + +.dark\:border-blue-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity)); +} + +.dark\:border-gray-600:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(75 85 99 / var(--tw-border-opacity)); +} + +.dark\:border-gray-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + +.dark\:border-green-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(21 128 61 / var(--tw-border-opacity)); +} + +.dark\:border-neutral-600:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(71 85 105 / var(--tw-border-opacity)); +} + +.dark\:border-neutral-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(51 65 85 / var(--tw-border-opacity)); +} + +.dark\:border-red-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(185 28 28 / var(--tw-border-opacity)); +} + +.dark\:border-yellow-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(161 98 7 / var(--tw-border-opacity)); +} + +.dark\:bg-blue-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +} + +.dark\:bg-gray-700:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.dark\:bg-gray-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.dark\:bg-green-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(20 83 45 / var(--tw-bg-opacity)); +} + +.dark\:bg-neutral-700:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(51 65 85 / var(--tw-bg-opacity)); +} + +.dark\:bg-neutral-800:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); +} + +.dark\:bg-neutral-800\/50:is(.dark *) { + background-color: rgb(30 41 59 / 0.5); +} + +.dark\:bg-neutral-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); +} + +.dark\:bg-primary-900\/30:is(.dark *) { + background-color: rgb(30 58 138 / 0.3); +} + +.dark\:bg-red-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(127 29 29 / var(--tw-bg-opacity)); +} + +.dark\:bg-yellow-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(113 63 18 / var(--tw-bg-opacity)); +} + +.dark\:text-blue-100:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(219 234 254 / var(--tw-text-opacity)); +} + +.dark\:text-gray-200:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + +.dark\:text-gray-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.dark\:text-green-100:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(220 252 231 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-100:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(241 245 249 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-200:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(226 232 240 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-300:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(203 213 225 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(148 163 184 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-50:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(248 250 252 / var(--tw-text-opacity)); +} + +.dark\:text-neutral-500:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.dark\:text-primary-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); +} + +.dark\:text-red-100:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(254 226 226 / var(--tw-text-opacity)); +} + +.dark\:text-white:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.dark\:text-yellow-100:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(254 249 195 / var(--tw-text-opacity)); +} + +.dark\:placeholder-neutral-500:is(.dark *)::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(100 116 139 / var(--tw-placeholder-opacity)); +} + +.dark\:placeholder-neutral-500:is(.dark *)::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(100 116 139 / var(--tw-placeholder-opacity)); +} + +.dark\:hover\:bg-gray-600:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)); +} + +.dark\:hover\:bg-neutral-600:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(71 85 105 / var(--tw-bg-opacity)); +} + +.dark\:hover\:bg-neutral-700:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(51 65 85 / var(--tw-bg-opacity)); +} + +.dark\:hover\:bg-neutral-800\/50:hover:is(.dark *) { + background-color: rgb(30 41 59 / 0.5); +} + +.dark\:hover\:text-blue-400:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-neutral-300:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(203 213 225 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-primary-300:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-primary-400:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); +} + +@media (min-width: 640px) { + .sm\:col-span-1 { + grid-column: span 1 / span 1; + } + + .sm\:inline-block { + display: inline-block; + } + + .sm\:inline { + display: inline; + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:items-center { + align-items: center; + } + + .sm\:justify-between { + justify-content: space-between; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } +} + +@media (min-width: 768px) { + .md\:col-span-1 { + grid-column: span 1 / span 1; + } + + .md\:col-span-3 { + grid-column: span 3 / span 3; + } + + .md\:flex { + display: flex; + } + + .md\:hidden { + display: none; + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } +} + +@media (min-width: 1024px) { + .lg\:col-span-1 { + grid-column: span 1 / span 1; + } + + .lg\:col-span-2 { + grid-column: span 2 / span 2; + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .lg\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } +} diff --git a/static/css/themes.css b/static/css/themes.css deleted file mode 100644 index 5c23bd8..0000000 --- a/static/css/themes.css +++ /dev/null @@ -1,198 +0,0 @@ -/* Delphi Database System - Theme Styles */ - -/* Light Theme (Default) */ -:root { - --delphi-primary: #0d6efd; - --delphi-primary-dark: #0b5ed7; - --delphi-primary-light: #6ea8fe; - --delphi-secondary: #6c757d; - --delphi-success: #198754; - --delphi-info: #0dcaf0; - --delphi-warning: #ffc107; - --delphi-danger: #dc3545; - --delphi-light: #f8f9fa; - --delphi-dark: #212529; - - /* Background colors */ - --bg-primary: #ffffff; - --bg-secondary: #f8f9fa; - --bg-tertiary: #e9ecef; - - /* Text colors */ - --text-primary: #212529; - --text-secondary: #6c757d; - --text-muted: #868e96; - - /* Border colors */ - --border-color: #dee2e6; - --border-light: #f8f9fa; - - /* Shadow */ - --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); - --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); - --shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); -} - -/* Dark Theme */ -[data-theme="dark"] { - --delphi-primary: #6ea8fe; - --delphi-primary-dark: #0b5ed7; - --delphi-primary-light: #9ec5fe; - --delphi-secondary: #adb5bd; - --delphi-success: #20c997; - --delphi-info: #39d7f0; - --delphi-warning: #ffcd39; - --delphi-danger: #ea868f; - --delphi-light: #495057; - --delphi-dark: #f8f9fa; - - /* Background colors */ - --bg-primary: #212529; - --bg-secondary: #343a40; - --bg-tertiary: #495057; - - /* Text colors */ - --text-primary: #f8f9fa; - --text-secondary: #adb5bd; - --text-muted: #6c757d; - - /* Border colors */ - --border-color: #495057; - --border-light: #343a40; - - /* Shadow */ - --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.25); - --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.35); - --shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.45); -} - -/* High Contrast Theme */ -[data-theme="high-contrast"] { - --delphi-primary: #0000ff; - --delphi-primary-dark: #000080; - --delphi-primary-light: #4040ff; - --delphi-secondary: #808080; - --delphi-success: #008000; - --delphi-info: #008080; - --delphi-warning: #ff8000; - --delphi-danger: #ff0000; - --delphi-light: #ffffff; - --delphi-dark: #000000; - - /* Background colors */ - --bg-primary: #ffffff; - --bg-secondary: #f0f0f0; - --bg-tertiary: #e0e0e0; - - /* Text colors */ - --text-primary: #000000; - --text-secondary: #404040; - --text-muted: #606060; - - /* Border colors */ - --border-color: #000000; - --border-light: #808080; - - /* Shadow */ - --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.5); - --shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.7); - --shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.8); -} - -/* Apply theme variables to components */ -body { - background-color: var(--bg-secondary); - color: var(--text-primary); -} - -.card { - background-color: var(--bg-primary); - border-color: var(--border-color); - box-shadow: var(--shadow-sm); -} - -.navbar-dark { - background-color: var(--delphi-primary) !important; -} - -.table { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -.table th { - background-color: var(--delphi-primary); - border-color: var(--border-color); -} - -.table td { - border-color: var(--border-color); -} - -.form-control { - background-color: var(--bg-primary); - border-color: var(--border-color); - color: var(--text-primary); -} - -.form-control:focus { - border-color: var(--delphi-primary); - box-shadow: 0 0 0 0.2rem rgba(var(--delphi-primary), 0.25); -} - -.modal-content { - background-color: var(--bg-primary); - border-color: var(--border-color); -} - -.modal-header { - background-color: var(--delphi-primary); - border-color: var(--border-color); -} - -.btn-primary { - background-color: var(--delphi-primary); - border-color: var(--delphi-primary); -} - -.btn-primary:hover { - background-color: var(--delphi-primary-dark); - border-color: var(--delphi-primary-dark); -} - -.alert { - border-color: var(--border-color); -} - -/* Theme transition */ -* { - transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; -} - -/* Print styles */ -@media print { - :root { - --delphi-primary: #000000; - --bg-primary: #ffffff; - --bg-secondary: #ffffff; - --text-primary: #000000; - --border-color: #000000; - } - - .navbar, .btn, .modal, .alert { - display: none !important; - } - - .card { - border: 1px solid #000000; - box-shadow: none; - } -} - -/* Reduced motion preferences */ -@media (prefers-reduced-motion: reduce) { - * { - transition: none !important; - animation: none !important; - } -} \ No newline at end of file diff --git a/static/images/delphi-logo.webp b/static/images/delphi-logo.webp new file mode 100644 index 0000000..d791e6a Binary files /dev/null and b/static/images/delphi-logo.webp differ diff --git a/static/js/alerts.js b/static/js/alerts.js new file mode 100644 index 0000000..7f85f14 --- /dev/null +++ b/static/js/alerts.js @@ -0,0 +1,191 @@ +// Shared alert/notification utility for consistent Tailwind styling and Font Awesome icons +// Provides: window.alerts.show(message, type?, options?) and compatibility shims + +(function () { + const TYPE_ALIASES = { + error: 'danger', + success: 'success', + warning: 'warning', + info: 'info', + danger: 'danger' + }; + + const TYPE_CLASSES = { + success: { + container: 'border-success-200 dark:border-success-800', + icon: 'fa-solid fa-circle-check text-success-600 dark:text-success-400' + }, + danger: { + container: 'border-danger-200 dark:border-danger-800', + icon: 'fa-solid fa-triangle-exclamation text-danger-600 dark:text-danger-400' + }, + warning: { + container: 'border-warning-200 dark:border-warning-800', + icon: 'fa-solid fa-triangle-exclamation text-warning-600 dark:text-warning-400' + }, + info: { + container: 'border-info-200 dark:border-info-800', + icon: 'fa-solid fa-circle-info text-info-600 dark:text-info-400' + } + }; + + function normalizeType(type) { + const key = String(type || 'info').toLowerCase(); + return TYPE_ALIASES[key] || 'info'; + } + + function getOrCreateContainer(containerId = 'notification-container') { + let container = document.getElementById(containerId); + if (!container) { + container = document.createElement('div'); + container.id = containerId; + container.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2 p-0'; + document.body.appendChild(container); + } + return container; + } + + function show(message, type = 'info', options = {}) { + const tone = normalizeType(type); + const { + duration = 5000, + dismissible = true, + containerId = 'notification-container', + role = 'alert', + ariaLive = 'polite', + html = false, + title = null, + actions = [], + onClose = null, + id = null + } = options; + + const container = getOrCreateContainer(containerId); + + const wrapper = document.createElement('div'); + wrapper.className = `alert-notification max-w-sm w-[22rem] bg-white dark:bg-neutral-800 border rounded-lg shadow-lg p-4 transition-all duration-300 translate-x-4 opacity-0 ${ + (TYPE_CLASSES[tone] || TYPE_CLASSES.info).container + }`; + wrapper.setAttribute('role', role); + wrapper.setAttribute('aria-live', ariaLive); + if (id) wrapper.id = id; + + const inner = document.createElement('div'); + inner.className = 'flex items-start'; + + const iconWrap = document.createElement('div'); + iconWrap.className = 'flex-shrink-0'; + const icon = document.createElement('i'); + icon.className = (TYPE_CLASSES[tone] || TYPE_CLASSES.info).icon; + iconWrap.appendChild(icon); + + const content = document.createElement('div'); + content.className = 'ml-3 flex-1'; + + if (title) { + const titleEl = document.createElement('p'); + titleEl.className = 'text-sm font-semibold text-neutral-900 dark:text-neutral-100'; + titleEl.textContent = String(title); + content.appendChild(titleEl); + } + + const text = document.createElement('div'); + text.className = 'text-xs mt-1 text-neutral-800 dark:text-neutral-200'; + if (message instanceof Node) { + text.appendChild(message); + } else if (html) { + text.innerHTML = String(message || ''); + } else { + text.textContent = String(message || ''); + } + content.appendChild(text); + + inner.appendChild(iconWrap); + inner.appendChild(content); + + if (dismissible) { + const closeWrap = document.createElement('div'); + closeWrap.className = 'ml-4 flex-shrink-0'; + const closeBtn = document.createElement('button'); + closeBtn.setAttribute('aria-label', 'Close'); + closeBtn.className = 'text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-colors'; + closeBtn.addEventListener('click', () => { + wrapper.remove(); + if (typeof onClose === 'function') onClose(); + }); + const x = document.createElement('i'); + x.className = 'fa-solid fa-xmark'; + closeBtn.appendChild(x); + closeWrap.appendChild(closeBtn); + inner.appendChild(closeWrap); + } + + // Actions (buttons) + if (Array.isArray(actions) && actions.length > 0) { + const actionsWrap = document.createElement('div'); + actionsWrap.className = 'mt-2 flex gap-2 flex-wrap'; + + actions.forEach((action) => { + if (!action || !action.label) return; + const btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = String(action.label); + if (action.ariaLabel) btn.setAttribute('aria-label', action.ariaLabel); + btn.className = action.classes || 'px-3 py-1 rounded text-xs transition-colors bg-neutral-200 hover:bg-neutral-300 text-neutral-800 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-200'; + btn.addEventListener('click', (ev) => { + try { + if (typeof action.onClick === 'function') { + action.onClick({ event: ev, wrapper }); + } + } finally { + if (action.autoClose !== false) { + wrapper.remove(); + if (typeof onClose === 'function') onClose(); + } + } + }); + actionsWrap.appendChild(btn); + }); + + content.appendChild(actionsWrap); + } + + wrapper.appendChild(inner); + container.appendChild(wrapper); + + // Animate in + requestAnimationFrame(() => { + wrapper.classList.remove('translate-x-4', 'opacity-0'); + }); + + if (duration > 0) { + setTimeout(() => { + wrapper.classList.add('translate-x-4', 'opacity-0'); + setTimeout(() => { + wrapper.remove(); + if (typeof onClose === 'function') onClose(); + }, 250); + }, duration); + } + + return wrapper; + } + + const alerts = { + show, + success: (message, options = {}) => show(message, 'success', options), + error: (message, options = {}) => show(message, 'danger', options), + warning: (message, options = {}) => show(message, 'warning', options), + info: (message, options = {}) => show(message, 'info', options), + getOrCreateContainer + }; + + // Expose globally + window.alerts = alerts; + // Backward-compatible shims + window.showAlert = (message, type = 'info', duration = 5000) => alerts.show(message, type, { duration }); + window.showNotification = (message, type = 'info', duration = 5000) => alerts.show(message, type, { duration }); + window.showToast = (message, type = 'info', duration = 3000) => alerts.show(message, type, { duration }); +})(); + + diff --git a/static/js/customers-tailwind.js b/static/js/customers-tailwind.js new file mode 100644 index 0000000..f6ffd25 --- /dev/null +++ b/static/js/customers-tailwind.js @@ -0,0 +1,560 @@ +// Customer management functionality - Tailwind version +let currentPage = 0; +let currentSearch = ''; +let isEditing = false; +let editingCustomerId = null; + +// Helper function for authenticated API calls +function getAuthHeaders() { + const token = localStorage.getItem('auth_token'); + return { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }; +} + +// Modal management functions +function showAddCustomerModal() { + isEditing = false; + editingCustomerId = null; + document.getElementById('customerModalLabel').textContent = 'Add New Customer'; + document.getElementById('deleteCustomerBtn').classList.add('hidden'); + clearCustomerForm(); + document.getElementById('customerModal').classList.remove('hidden'); +} + +function closeCustomerModal() { + document.getElementById('customerModal').classList.add('hidden'); +} + +function showEditCustomerModal() { + document.getElementById('customerModalLabel').textContent = 'Edit Customer'; + document.getElementById('deleteCustomerBtn').classList.remove('hidden'); + document.getElementById('customerModal').classList.remove('hidden'); +} + +// Enhanced table display function +function displayCustomers(customers) { + const tbody = document.getElementById('customersTableBody'); + const emptyState = document.getElementById('emptyState'); + + tbody.innerHTML = ''; + + if (!customers || customers.length === 0) { + emptyState.classList.remove('hidden'); + return; + } + + emptyState.classList.add('hidden'); + + customers.forEach(customer => { + const row = document.createElement('tr'); + row.className = 'hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors duration-150'; + + row.innerHTML = ` + +
${customer.id}
+ + +
+
+
+ ${getInitials(customer)} +
+
+
+
${formatName(customer)}
+ ${customer.title ? `
${customer.title}
` : ''} +
+
+ + + ${customer.group ? `${customer.group}` : '-'} + + + ${formatLocation(customer)} + + + ${formatPhones(customer.phone_numbers)} + + + ${customer.email ? `${customer.email}` : '-'} + + +
+ + +
+ + `; + tbody.appendChild(row); + }); +} + +// Helper functions +function getInitials(customer) { + const first = customer.first || ''; + const last = customer.last || ''; + return (first.charAt(0) + last.charAt(0)).toUpperCase(); +} + +function formatName(customer) { + const parts = []; + if (customer.prefix) parts.push(customer.prefix); + if (customer.first) parts.push(customer.first); + if (customer.middle) parts.push(customer.middle); + parts.push(customer.last); + if (customer.suffix) parts.push(customer.suffix); + return parts.join(' '); +} + +function formatLocation(customer) { + const parts = []; + if (customer.city) parts.push(customer.city); + if (customer.abrev) parts.push(customer.abrev); + return parts.join(', ') || '-'; +} + +function formatPhones(phones) { + if (!phones || phones.length === 0) return '-'; + + return phones.map(p => + `
+ ${p.location || 'Phone'}: + ${p.phone} +
` + ).join(''); +} + +// Enhanced pagination +function updatePagination(currentPage, totalPages) { + const pagination = document.getElementById('pagination'); + pagination.innerHTML = ''; + + if (totalPages <= 1) return; + + // Previous button + const prevButton = document.createElement('button'); + prevButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${ + currentPage === 0 + ? 'bg-neutral-100 dark:bg-neutral-800 text-neutral-400 dark:text-neutral-500 cursor-not-allowed' + : 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600' + }`; + prevButton.innerHTML = ''; + prevButton.disabled = currentPage === 0; + prevButton.onclick = () => currentPage > 0 && loadCustomers(currentPage - 1, currentSearch); + pagination.appendChild(prevButton); + + // Page numbers + const startPage = Math.max(0, currentPage - 2); + const endPage = Math.min(totalPages - 1, currentPage + 2); + + for (let i = startPage; i <= endPage; i++) { + const pageButton = document.createElement('button'); + pageButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${ + i === currentPage + ? 'bg-primary-600 text-white' + : 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600' + }`; + pageButton.textContent = i + 1; + pageButton.onclick = () => loadCustomers(i, currentSearch); + pagination.appendChild(pageButton); + } + + // Next button + const nextButton = document.createElement('button'); + nextButton.className = `px-3 py-2 text-sm rounded-lg transition-colors duration-200 ${ + currentPage === totalPages - 1 + ? 'bg-neutral-100 dark:bg-neutral-800 text-neutral-400 dark:text-neutral-500 cursor-not-allowed' + : 'bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-600 border border-neutral-200 dark:border-neutral-600' + }`; + nextButton.innerHTML = ''; + nextButton.disabled = currentPage === totalPages - 1; + nextButton.onclick = () => currentPage < totalPages - 1 && loadCustomers(currentPage + 1, currentSearch); + pagination.appendChild(nextButton); +} + +// Enhanced alert function (delegates to shared alerts utility) +function showAlert(message, type = 'info') { + if (window.alerts && typeof window.alerts.show === 'function') { + window.alerts.show(message, type); + return; + } + // Fallback + alert(String(message)); +} + +// Close modal when clicking outside +document.addEventListener('click', function(event) { + const modal = document.getElementById('customerModal'); + if (event.target === modal) { + closeCustomerModal(); + } +}); + +// Handle escape key for modal +document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeCustomerModal(); + } +}); + +// Make functions globally available for backwards compatibility +window.showAddCustomerModal = showAddCustomerModal; +window.closeCustomerModal = closeCustomerModal; +window.showEditCustomerModal = showEditCustomerModal; +window.displayCustomers = displayCustomers; +window.showAlert = showAlert; + +// Form handling functions +function clearCustomerForm() { + document.getElementById('customerId').value = ''; + document.getElementById('customerId').disabled = false; + document.getElementById('last').value = ''; + document.getElementById('first').value = ''; + document.getElementById('middle').value = ''; + document.getElementById('prefix').value = ''; + document.getElementById('suffix').value = ''; + document.getElementById('title').value = ''; + document.getElementById('group').value = ''; + document.getElementById('a1').value = ''; + document.getElementById('a2').value = ''; + document.getElementById('a3').value = ''; + document.getElementById('city').value = ''; + document.getElementById('abrev').value = ''; + document.getElementById('zip').value = ''; + document.getElementById('email').value = ''; + document.getElementById('dob').value = ''; + document.getElementById('ss_number').value = ''; + document.getElementById('legal_status').value = ''; + document.getElementById('memo').value = ''; + document.getElementById('phoneList').innerHTML = ''; + window.currentPhones = []; // Track phones with {id, location, phone, action: 'add'|'update'|'delete'|'none'} +} + +async function populateCustomerForm(customer) { + document.getElementById('customerId').value = customer.id; + document.getElementById('customerId').disabled = true; + document.getElementById('last').value = customer.last || ''; + document.getElementById('first').value = customer.first || ''; + document.getElementById('middle').value = customer.middle || ''; + document.getElementById('prefix').value = customer.prefix || ''; + document.getElementById('suffix').value = customer.suffix || ''; + document.getElementById('title').value = customer.title || ''; + document.getElementById('group').value = customer.group || ''; + document.getElementById('a1').value = customer.a1 || ''; + document.getElementById('a2').value = customer.a2 || ''; + document.getElementById('a3').value = customer.a3 || ''; + document.getElementById('city').value = customer.city || ''; + document.getElementById('abrev').value = customer.abrev || ''; + document.getElementById('zip').value = customer.zip || ''; + document.getElementById('email').value = customer.email || ''; + document.getElementById('dob').value = customer.dob ? new Date(customer.dob).toISOString().split('T')[0] : ''; + document.getElementById('ss_number').value = customer.ss_number || ''; + document.getElementById('legal_status').value = customer.legal_status || ''; + document.getElementById('memo').value = customer.memo || ''; + + // Populate phones + const phoneList = document.getElementById('phoneList'); + phoneList.innerHTML = ''; + window.currentPhones = customer.phone_numbers.map(p => ({...p, action: 'none'})); + window.currentPhones.forEach((phone, index) => addPhoneRow(index, phone)); +} + +function addPhoneRow(index, phone = {location: '', phone: '', action: 'add'}) { + const phoneList = document.getElementById('phoneList'); + const row = document.createElement('div'); + row.className = 'flex items-end gap-4 mb-2'; + row.dataset.index = index; + row.innerHTML = ` +
+ + +
+
+ + +
+ + `; + phoneList.appendChild(row); + + // Event listeners for changes + row.querySelector('.phone-location').addEventListener('input', () => updatePhone(index)); + row.querySelector('.phone-number').addEventListener('input', () => updatePhone(index)); + row.querySelector('.remove-phone').addEventListener('click', () => removePhone(index)); +} + +function updatePhone(index) { + const row = document.querySelector(`[data-index="${index}"]`); + const location = row.querySelector('.phone-location').value; + const phone = row.querySelector('.phone-number').value; + const current = window.currentPhones[index]; + if (current) { + current.location = location; + current.phone = phone; + if (current.action === 'none') current.action = 'update'; + } +} + +function removePhone(index) { + const row = document.querySelector(`[data-index="${index}"]`); + row.remove(); + const phone = window.currentPhones[index]; + if (phone.id) { + phone.action = 'delete'; + } else { + window.currentPhones.splice(index, 1); + } + // Re-index rows + document.querySelectorAll('#phoneList > div').forEach((r, i) => { + r.dataset.index = i; + }); +} + +document.getElementById('addPhoneBtn').addEventListener('click', () => { + const index = window.currentPhones.length; + window.currentPhones.push({location: '', phone: '', action: 'add'}); + addPhoneRow(index); +}); + +// Validation +async function validateForm() { + let isValid = true; + const requiredFields = ['customerId', 'last']; + requiredFields.forEach(id => { + const input = document.getElementById(id); + if (!input.value.trim()) { + isValid = false; + input.classList.add('border-danger-500'); + } else { + input.classList.remove('border-danger-500'); + } + }); + + // Email validation + const email = document.getElementById('email'); + if (email.value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) { + isValid = false; + email.classList.add('border-danger-500'); + } else { + email.classList.remove('border-danger-500'); + } + + // Check unique ID for create + if (!isEditing) { + try { + const response = await fetch(`/api/customers/${document.getElementById('customerId').value}`); + if (response.ok) { + isValid = false; + showAlert('Customer ID already exists', 'danger'); + document.getElementById('customerId').classList.add('border-danger-500'); + } + } catch (error) {} + } + + return isValid; +} + +// Save customer +async function saveCustomer() { + if (!await validateForm()) return; + + const customerData = { + id: document.getElementById('customerId').value, + last: document.getElementById('last').value, + first: document.getElementById('first').value, + middle: document.getElementById('middle').value, + prefix: document.getElementById('prefix').value, + suffix: document.getElementById('suffix').value, + title: document.getElementById('title').value, + group: document.getElementById('group').value, + a1: document.getElementById('a1').value, + a2: document.getElementById('a2').value, + a3: document.getElementById('a3').value, + city: document.getElementById('city').value, + abrev: document.getElementById('abrev').value, + zip: document.getElementById('zip').value, + email: document.getElementById('email').value, + dob: document.getElementById('dob').value || null, + ss_number: document.getElementById('ss_number').value, + legal_status: document.getElementById('legal_status').value, + memo: document.getElementById('memo').value + }; + + try { + let response; + if (isEditing) { + response = await fetch(`/api/customers/${editingCustomerId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify(customerData) + }); + } else { + response = await fetch('/api/customers/', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify(customerData) + }); + } + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || 'Failed to save customer'); + } + + const savedCustomer = await response.json(); + + // Handle phones + for (const phone of window.currentPhones) { + if (phone.action === 'add') { + await fetch(`/api/customers/${savedCustomer.id}/phones`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({location: phone.location, phone: phone.phone}) + }); + } else if (phone.action === 'update') { + await fetch(`/api/customers/${savedCustomer.id}/phones/${phone.id}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify({location: phone.location, phone: phone.phone}) + }); + } else if (phone.action === 'delete') { + await fetch(`/api/customers/${savedCustomer.id}/phones/${phone.id}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + } + } + + showAlert(`Customer ${isEditing ? 'updated' : 'created'} successfully`, 'success'); + closeCustomerModal(); + loadCustomers(currentPage, currentSearch); + + } catch (error) { + showAlert(`Error saving customer: ${error.message}`, 'danger'); + } +} + +// Edit customer +async function editCustomer(customerId) { + try { + const response = await fetch(`/api/customers/${customerId}`, { + headers: getAuthHeaders() + }); + if (!response.ok) throw new Error('Failed to load customer'); + + const customer = await response.json(); + isEditing = true; + editingCustomerId = customerId; + populateCustomerForm(customer); + showEditCustomerModal(); + + } catch (error) { + showAlert(`Error loading customer: ${error.message}`, 'danger'); + } +} + +// Delete customer +async function deleteCustomer() { + if (!confirm('Are you sure you want to delete this customer?')) return; + + try { + const response = await fetch(`/api/customers/${editingCustomerId}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + + if (!response.ok) throw new Error('Failed to delete customer'); + + showAlert('Customer deleted successfully', 'success'); + closeCustomerModal(); + loadCustomers(currentPage, currentSearch); + + } catch (error) { + showAlert(`Error deleting customer: ${error.message}`, 'danger'); + } +} + +// Populate datalists +async function populateDatalists() { + try { + const groupsResp = await fetch('/api/customers/groups', {headers: getAuthHeaders()}); + const groups = await groupsResp.json(); + const groupList = document.getElementById('groupList'); + groups.forEach(g => { + const option = document.createElement('option'); + option.value = g.group; + groupList.appendChild(option); + }); + + const statesResp = await fetch('/api/customers/states', {headers: getAuthHeaders()}); + const states = await statesResp.json(); + const stateList = document.getElementById('stateList'); + states.forEach(s => { + const option = document.createElement('option'); + option.value = s.state; + stateList.appendChild(option); + }); + } catch (error) { + console.error('Error loading datalists:', error); + } +} + +// Update setupEventListeners +function setupEventListeners() { + // Existing listeners... + document.getElementById('searchBtn').addEventListener('click', performSearch); + document.getElementById('searchInput').addEventListener('keypress', function(e) { + if (e.key === 'Enter') performSearch(); + }); + + document.getElementById('phoneSearchBtn').addEventListener('click', performPhoneSearch); + document.getElementById('phoneSearch').addEventListener('keypress', function(e) { + if (e.key === 'Enter') performPhoneSearch(); + }); + + document.getElementById('addCustomerBtn').addEventListener('click', showAddCustomerModal); + document.getElementById('saveCustomerBtn').addEventListener('click', saveCustomer); + document.getElementById('deleteCustomerBtn').addEventListener('click', deleteCustomer); + document.getElementById('statsBtn').addEventListener('click', showStats); + + // Form validation on blur for customerId + document.getElementById('customerId').addEventListener('blur', async function() { + if (!isEditing && this.value) { + try { + const response = await fetch(`/api/customers/${this.value}`); + if (response.ok) { + showAlert('Customer ID already exists', 'warning'); + this.classList.add('border-danger-500'); + } else { + this.classList.remove('border-danger-500'); + } + } catch (error) {} + } + }); +} + +// Update DOMContentLoaded +document.addEventListener('DOMContentLoaded', function() { + const token = localStorage.getItem('auth_token'); + if (!token) { + window.location.href = '/login'; + return; + } + + loadCustomers(); + loadGroups(); + loadStates(); + populateDatalists(); + setupEventListeners(); + clearCustomerForm(); // Initial clear +}); \ No newline at end of file diff --git a/static/js/financial.js b/static/js/financial.js new file mode 100644 index 0000000..237eb1a --- /dev/null +++ b/static/js/financial.js @@ -0,0 +1,18 @@ +// financial.js + +// Assuming this is a new file + +// Copy and adapt the script from financial.html + +// ... add the JS content ... + +// Modify modal showing/hiding to use classList.add/remove('hidden') instead of Bootstrap modal + +// For example: +function showQuickTimeModal() { + document.getElementById('quickTimeModal').classList.remove('hidden'); +} + +// Add event listeners for close buttons, etc. + +// ... complete the JS ... diff --git a/static/js/keyboard-shortcuts.js b/static/js/keyboard-shortcuts.js index b95409e..d8c062f 100644 --- a/static/js/keyboard-shortcuts.js +++ b/static/js/keyboard-shortcuts.js @@ -257,7 +257,7 @@ function focusGlobalSearch() { // Form action functions function newRecord() { - const newBtn = document.querySelector('.btn-new, [data-action="new"], .btn-primary[href*="new"]'); + const newBtn = document.querySelector('.btn-new, [data-action="new"], .bg-primary-600[href*="new"]'); if (newBtn) { newBtn.click(); } else { @@ -266,7 +266,7 @@ function newRecord() { } function saveRecord() { - const saveBtn = document.querySelector('.btn-save, [data-action="save"], .btn-success[type="submit"]'); + const saveBtn = document.querySelector('.btn-save, [data-action="save"], .bg-green-600[type="submit"]'); if (saveBtn) { saveBtn.click(); } else { @@ -290,7 +290,7 @@ function editMode() { } function completeAction() { - const completeBtn = document.querySelector('.btn-complete, [data-action="complete"], .btn-primary'); + const completeBtn = document.querySelector('.btn-complete, [data-action="complete"], .bg-primary-600'); if (completeBtn) { completeBtn.click(); } else { @@ -313,7 +313,7 @@ function clearForm() { } function deleteRecord() { - const deleteBtn = document.querySelector('.btn-delete, [data-action="delete"], .btn-danger'); + const deleteBtn = document.querySelector('.btn-delete, [data-action="delete"], .bg-danger-600'); if (deleteBtn) { deleteBtn.click(); } else { @@ -323,17 +323,15 @@ function deleteRecord() { function cancelAction() { // Close modals first - const modal = document.querySelector('.modal.show'); - if (modal) { - const bsModal = bootstrap.Modal.getInstance(modal); - if (bsModal) { - bsModal.hide(); - return; - } + // Close Tailwind-style modals + const openModal = document.querySelector('.fixed.inset-0:not(.hidden)'); + if (openModal) { + openModal.classList.add('hidden'); + return; } // Then try cancel buttons - const cancelBtn = document.querySelector('.btn-cancel, [data-action="cancel"], .btn-secondary'); + const cancelBtn = document.querySelector('.btn-cancel, [data-action="cancel"], .bg-neutral-100'); if (cancelBtn) { cancelBtn.click(); } else { @@ -345,21 +343,24 @@ function cancelAction() { function showHelp() { const helpModal = document.querySelector('#shortcutsModal'); if (helpModal) { - const modal = new bootstrap.Modal(helpModal); - modal.show(); + helpModal.classList.remove('hidden'); } else { showToast('Press F1 to see keyboard shortcuts', 'info'); } } function showMenu() { - // Toggle main navigation menu on mobile or show dropdown - const navbarToggler = document.querySelector('.navbar-toggler'); - if (navbarToggler && !navbarToggler.classList.contains('collapsed')) { - navbarToggler.click(); - } else { - showToast('Menu (F10) - Use Alt+C, Alt+F, Alt+L, Alt+D for navigation', 'info'); + // Toggle Tailwind mobile menu if available + if (typeof toggleMobileMenu === 'function') { + toggleMobileMenu(); + return; } + const mobileMenu = document.getElementById('mobileMenu'); + if (mobileMenu) { + mobileMenu.classList.toggle('hidden'); + return; + } + showToast('Menu (F10) - Use Alt+C, Alt+F, Alt+L, Alt+D for navigation', 'info'); } function showMemo() { @@ -446,38 +447,12 @@ function openRecord() { } function showToast(message, type = 'info') { - // Create toast element - const toastHtml = ` - - `; - - // Get or create toast container - let toastContainer = document.querySelector('.toast-container'); - if (!toastContainer) { - toastContainer = document.createElement('div'); - toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3'; - document.body.appendChild(toastContainer); + if (window.alerts && typeof window.alerts.show === 'function') { + window.alerts.show(message, type, { duration: 3000 }); + return; } - - // Add toast - const toastWrapper = document.createElement('div'); - toastWrapper.innerHTML = toastHtml; - const toastElement = toastWrapper.firstElementChild; - toastContainer.appendChild(toastElement); - - // Show toast - const toast = new bootstrap.Toast(toastElement, { delay: 3000 }); - toast.show(); - - // Remove toast element after it's hidden - toastElement.addEventListener('hidden.bs.toast', () => { - toastElement.remove(); - }); + // Fallback + alert(String(message)); } // Export for use in other scripts diff --git a/static/js/main.js b/static/js/main.js index 8315f72..c1cfffd 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -20,17 +20,7 @@ async function initializeApp() { window.keyboardShortcuts.initialize(); } - // Initialize tooltips - const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl); - }); - - // Initialize popovers - const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); - popoverTriggerList.map(function (popoverTriggerEl) { - return new bootstrap.Popover(popoverTriggerEl); - }); + // Remove Bootstrap-dependent tooltips/popovers; use native title/tooltips if needed // Add form validation classes initializeFormValidation(); @@ -44,19 +34,19 @@ async function initializeApp() { // Form validation function initializeFormValidation() { - // Add Bootstrap validation styles - const forms = document.querySelectorAll('form.needs-validation'); + // Native validation handling without Bootstrap classes + const forms = document.querySelectorAll('form'); forms.forEach(form => { form.addEventListener('submit', function(event) { if (!form.checkValidity()) { event.preventDefault(); event.stopPropagation(); + form.reportValidity(); } - form.classList.add('was-validated'); }); }); - // Real-time validation for specific fields + // Real-time validation for required fields (Tailwind styles) const requiredFields = document.querySelectorAll('input[required], select[required], textarea[required]'); requiredFields.forEach(field => { field.addEventListener('blur', function() { @@ -67,15 +57,8 @@ function initializeFormValidation() { function validateField(field) { const isValid = field.checkValidity(); - field.classList.remove('is-valid', 'is-invalid'); - field.classList.add(isValid ? 'is-valid' : 'is-invalid'); - - // Show/hide custom feedback - const feedback = field.parentNode.querySelector('.invalid-feedback'); - if (feedback) { - feedback.classList.toggle('hidden', isValid); - feedback.classList.toggle('visible', !isValid); - } + field.setAttribute('aria-invalid', String(!isValid)); + field.classList.toggle('border-danger-500', !isValid); } // API helpers @@ -157,45 +140,18 @@ function logout() { window.location.href = '/login'; } -// Notification system +// Notification system (delegates to shared alerts utility) function showNotification(message, type = 'info', duration = 5000) { - const notificationContainer = getOrCreateNotificationContainer(); - - const notification = document.createElement('div'); - notification.className = `alert alert-${type} alert-dismissible fade show`; - notification.setAttribute('role', 'alert'); - notification.innerHTML = ` - ${message} - - `; - - notificationContainer.appendChild(notification); - - // Auto-dismiss after duration - if (duration > 0) { - setTimeout(() => { - notification.remove(); - }, duration); + if (window.alerts && typeof window.alerts.show === 'function') { + return window.alerts.show(message, type, { duration }); } - - return notification; -} - -function getOrCreateNotificationContainer() { - let container = document.querySelector('#notification-container'); - if (!container) { - container = document.createElement('div'); - container.id = 'notification-container'; - container.className = 'position-fixed top-0 end-0 p-3'; - container.classList.add('notification-container'); - document.body.appendChild(container); - } - return container; + // Fallback if alerts module not yet loaded + return alert(String(message)); } // Loading states function showLoading(element, text = 'Loading...') { - const spinner = ``; + const spinner = ``; const originalContent = element.innerHTML; element.innerHTML = `${spinner}${text}`; element.disabled = true; @@ -271,11 +227,12 @@ function addRowSelection(table) { tbody.addEventListener('click', function(e) { const row = e.target.closest('tr'); if (row && e.target.type !== 'checkbox') { - row.classList.toggle('table-active'); + const isSelected = row.classList.toggle('bg-neutral-100'); + row.classList.toggle('dark:bg-neutral-700', isSelected); // Trigger custom event const event = new CustomEvent('rowSelect', { - detail: { row, selected: row.classList.contains('table-active') } + detail: { row, selected: isSelected } }); table.dispatchEvent(event); } @@ -342,18 +299,18 @@ function initializeSearch(searchInput, resultsContainer, searchFunction) { function displaySearchResults(container, results) { if (!results || results.length === 0) { - container.innerHTML = '

No results found

'; + container.innerHTML = '

No results found

'; return; } const resultsHtml = results.map(result => `
-
+
${result.title} - ${result.description} + ${result.description}
- ${result.type} + ${result.type}
`).join(''); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..611536b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,119 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./templates/**/*.html", + "./static/js/**/*.js", + ], + darkMode: 'class', + theme: { + extend: { + colors: { + // Primary brand colors + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + 950: '#172554', + }, + // Neutral grays + neutral: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', + 950: '#020617', + }, + // Semantic colors + success: { + 50: '#f0fdf4', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + }, + warning: { + 50: '#fffbeb', + 500: '#f59e0b', + 600: '#d97706', + 700: '#b45309', + }, + danger: { + 50: '#fef2f2', + 500: '#ef4444', + 600: '#dc2626', + 700: '#b91c1c', + }, + info: { + 50: '#f0f9ff', + 500: '#06b6d4', + 600: '#0891b2', + 700: '#0e7490', + }, + }, + fontFamily: { + sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'], + }, + fontSize: { + 'xs': ['0.75rem', { lineHeight: '1rem' }], + 'sm': ['0.875rem', { lineHeight: '1.25rem' }], + 'base': ['1rem', { lineHeight: '1.5rem' }], + 'lg': ['1.125rem', { lineHeight: '1.75rem' }], + 'xl': ['1.25rem', { lineHeight: '1.75rem' }], + '2xl': ['1.5rem', { lineHeight: '2rem' }], + '3xl': ['1.875rem', { lineHeight: '2.25rem' }], + }, + spacing: { + '18': '4.5rem', + '88': '22rem', + '128': '32rem', + }, + borderRadius: { + 'xl': '0.75rem', + '2xl': '1rem', + '3xl': '1.5rem', + }, + boxShadow: { + 'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)', + 'medium': '0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + 'large': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + }, + animation: { + 'fade-in': 'fadeIn 0.3s ease-in-out', + 'slide-in': 'slideIn 0.3s ease-in-out', + 'scale-in': 'scaleIn 0.2s ease-in-out', + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0', transform: 'translateY(8px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + slideIn: { + '0%': { opacity: '0', transform: 'translateY(20px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + scaleIn: { + '0%': { opacity: '0', transform: 'scale(0.95)' }, + '100%': { opacity: '1', transform: 'scale(1)' }, + }, + }, + backdropBlur: { + xs: '2px', + }, + }, + }, + plugins: [ + require('@tailwindcss/forms'), + ], +} \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html index 67b250f..3c5e607 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -2,182 +2,144 @@ {% block title %}System Administration{% endblock %} -{% block content %} -
-
-
-
-

System Administration

-
- - -
-
-
-
+{% block bridge_css %}{% endblock %} - -
-
-
-
-
+{% block content %} +
+
+
+
+
+
-
System Status
-

Healthy

+
System Status
+

Healthy

-
-
-
-
+
+
+
+
-
Total Users
-

0

+
Total Users
+

0

-
-
-
-
+
+
+
+
-
Database Size
-

0 MB

+
Database Size
+

0 MB

-
-
-
-
+
+
+
+
-
System Uptime
-

Unknown

+
System Uptime
+

Unknown

-