Browse Source

Refactor: Modularized backend, removed Metal/SLS tech, and added Pytest suite

unknown 1 tuần trước cách đây
commit
4479904d2a
91 tập tin đã thay đổi với 17663 bổ sung0 xóa
  1. 31 0
      .gitignore
  2. 1 0
      backend/__init__.py
  3. 6 0
      backend/alter_db.py
  4. 6 0
      backend/alter_db2.py
  5. 6 0
      backend/alter_db3.py
  6. 7 0
      backend/alter_db4.py
  7. 17 0
      backend/alter_db_chat.py
  8. 29 0
      backend/alter_db_missing.py
  9. 22 0
      backend/alter_db_orders.py
  10. 45 0
      backend/auth_utils.py
  11. 30 0
      backend/config.py
  12. 67 0
      backend/db.py
  13. 42 0
      backend/init_db.py
  14. 50 0
      backend/locales.py
  15. 60 0
      backend/main.py
  16. 19 0
      backend/notifications.py
  17. 45 0
      backend/preview_utils.py
  18. 12 0
      backend/printer_profile.ini
  19. 15 0
      backend/requirements.txt
  20. 40 0
      backend/reseed.py
  21. 7 0
      backend/routers/__init__.py
  22. 109 0
      backend/routers/auth.py
  23. 96 0
      backend/routers/catalog.py
  24. 62 0
      backend/routers/chat.py
  25. 50 0
      backend/routers/files.py
  26. 213 0
      backend/routers/orders.py
  27. 57 0
      backend/routers/portfolio.py
  28. 102 0
      backend/schema.sql
  29. 152 0
      backend/schemas.py
  30. 1 0
      backend/services/__init__.py
  31. 39 0
      backend/services/chat_manager.py
  32. 27 0
      backend/services/order_processing.py
  33. 27 0
      backend/services/pricing.py
  34. 30 0
      backend/session_utils.py
  35. 66 0
      backend/slicer_utils.py
  36. 1 0
      backend/tests/__init__.py
  37. 33 0
      backend/tests/conftest.py
  38. 46 0
      backend/tests/test_auth.py
  39. 16 0
      backend/tests/test_catalog.py
  40. 52 0
      backend/tests/test_orders.py
  41. 20 0
      build_frontend.sh
  42. 35 0
      fix_users_table.py
  43. 14 0
      index.html
  44. 3321 0
      package-lock.json
  45. 45 0
      package.json
  46. 6 0
      postcss.config.js
  47. 32 0
      radionica-backend.service
  48. 108 0
      scripts/manage_locales.py
  49. BIN
      server_debug.log
  50. 42 0
      src/App.css
  51. 19 0
      src/App.vue
  52. BIN
      src/assets/hero-3d-print.jpg
  53. 47 0
      src/components/CTASection.vue
  54. 127 0
      src/components/CompleteProfileModal.vue
  55. 91 0
      src/components/Footer.vue
  56. 167 0
      src/components/Header.vue
  57. 71 0
      src/components/HeroSection.vue
  58. 60 0
      src/components/LanguageSwitcher.vue
  59. 17 0
      src/components/Logo.vue
  60. 371 0
      src/components/ModelUploadSection.vue
  61. 228 0
      src/components/OrderChat.vue
  62. 49 0
      src/components/ProcessSection.vue
  63. 82 0
      src/components/QuotingSection.vue
  64. 90 0
      src/components/ServicesSection.vue
  65. 59 0
      src/components/ui/button.vue
  66. 21 0
      src/i18n.ts
  67. 174 0
      src/index.css
  68. 403 0
      src/lib/api.ts
  69. 6 0
      src/lib/utils.ts
  70. 161 0
      src/locales/en.json
  71. 161 0
      src/locales/me.json
  72. 161 0
      src/locales/ru.json
  73. 629 0
      src/locales/translations.json
  74. 14 0
      src/main.ts
  75. 473 0
      src/pages/Admin.vue
  76. 240 0
      src/pages/Auth.vue
  77. 25 0
      src/pages/Index.vue
  78. 15 0
      src/pages/NotFound.vue
  79. 208 0
      src/pages/Orders.vue
  80. 96 0
      src/pages/Portfolio.vue
  81. 21 0
      src/router/index.ts
  82. 51 0
      src/stores/auth.ts
  83. 7723 0
      src/tailwind.config.lov.json
  84. 85 0
      tailwind.config.ts
  85. 30 0
      test_auth.py
  86. 17 0
      tmp_update_orders_notes.py
  87. 29 0
      tmp_update_orders_schema.py
  88. 37 0
      tmp_update_schema.py
  89. 24 0
      tsconfig.json
  90. 10 0
      tsconfig.node.json
  91. 12 0
      vite.config.ts

+ 31 - 0
.gitignore

@@ -0,0 +1,31 @@
+# Node
+node_modules/
+dist/
+.env
+
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+.venv/
+venv/
+ENV/
+.pytest_cache/
+.coverage
+htmlcov/
+
+# Local data
+uploads/
+backend/uploads/
+*.db
+*.sqlite3
+
+# OS
+.DS_Store
+Thumbs.db
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo

+ 1 - 0
backend/__init__.py

@@ -0,0 +1 @@
+# Backend package

+ 6 - 0
backend/alter_db.py

@@ -0,0 +1,6 @@
+import db
+try:
+    db.execute_commit('ALTER TABLE order_files ADD COLUMN quantity INT DEFAULT 1')
+    print("Column added")
+except Exception as e:
+    print("Error:", e)

+ 6 - 0
backend/alter_db2.py

@@ -0,0 +1,6 @@
+import db
+try:
+    db.execute_commit('ALTER TABLE order_files ADD COLUMN file_hash VARCHAR(255) NULL')
+    print("Hash column added")
+except Exception as e:
+    print("Error:", e)

+ 6 - 0
backend/alter_db3.py

@@ -0,0 +1,6 @@
+import db
+try:
+    db.execute_commit('ALTER TABLE order_files MODIFY order_id INT NULL')
+    print("order_id made nullable")
+except Exception as e:
+    print("Error:", e)

+ 7 - 0
backend/alter_db4.py

@@ -0,0 +1,7 @@
+import db
+try:
+    db.execute_commit('ALTER TABLE order_files ADD COLUMN print_time VARCHAR(50) NULL')
+    db.execute_commit('ALTER TABLE order_files ADD COLUMN filament_g DECIMAL(10,2) NULL')
+    print("Added metrics columns")
+except Exception as e:
+    print("Error:", e)

+ 17 - 0
backend/alter_db_chat.py

@@ -0,0 +1,17 @@
+import db
+try:
+    query = """
+    CREATE TABLE IF NOT EXISTS order_messages (
+        id INT AUTO_INCREMENT PRIMARY KEY,
+        order_id INT NOT NULL,
+        user_id INT,
+        is_from_admin BOOLEAN DEFAULT FALSE,
+        message TEXT NOT NULL,
+        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
+    );
+    """
+    db.execute_commit(query)
+    print("order_messages table created successfully")
+except Exception as e:
+    print("Error:", e)

+ 29 - 0
backend/alter_db_missing.py

@@ -0,0 +1,29 @@
+import db
+
+queries = [
+    """CREATE TABLE IF NOT EXISTS order_photos (
+        id INT AUTO_INCREMENT PRIMARY KEY,
+        order_id INT NOT NULL,
+        file_path VARCHAR(512) NOT NULL,
+        is_public BOOLEAN DEFAULT FALSE,
+        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
+    )""",
+    """CREATE TABLE IF NOT EXISTS order_messages (
+        id INT AUTO_INCREMENT PRIMARY KEY,
+        order_id INT NOT NULL,
+        user_id INT,
+        is_from_admin BOOLEAN DEFAULT FALSE,
+        message TEXT NOT NULL,
+        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
+    )""",
+]
+
+for q in queries:
+    try:
+        db.execute_commit(q)
+        name = q.split("TABLE IF NOT EXISTS ")[1].split(" ")[0].split("(")[0]
+        print(f"  + {name} OK")
+    except Exception as e:
+        print(f"  ! {e}")

+ 22 - 0
backend/alter_db_orders.py

@@ -0,0 +1,22 @@
+import db
+
+queries = [
+    "ALTER TABLE orders ADD COLUMN material_name VARCHAR(100) NULL",
+    "ALTER TABLE orders ADD COLUMN material_price DECIMAL(10,4) DEFAULT NULL",
+    "ALTER TABLE orders ADD COLUMN quantity INT DEFAULT 1",
+    "ALTER TABLE orders ADD COLUMN notes TEXT",
+    "ALTER TABLE orders ADD COLUMN allow_portfolio BOOLEAN DEFAULT FALSE",
+    "ALTER TABLE orders ADD COLUMN estimated_price DECIMAL(10,2) DEFAULT NULL",
+]
+
+for q in queries:
+    try:
+        db.execute_commit(q)
+        col = q.split("ADD COLUMN ")[1].split(" ")[0]
+        print(f"  + {col} added")
+    except Exception as e:
+        if "Duplicate column" in str(e):
+            col = q.split("ADD COLUMN ")[1].split(" ")[0]
+            print(f"  ~ {col} already exists")
+        else:
+            print(f"  ! Error: {e}")

+ 45 - 0
backend/auth_utils.py

@@ -0,0 +1,45 @@
+from passlib.context import CryptContext
+from jose import JWTError, jwt
+from datetime import datetime, timedelta
+from typing import Optional
+from fastapi.security import OAuth2PasswordBearer
+import session_utils
+
+# Configuration
+SECRET_KEY = "your-secret-key-replace-with-env-variable"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 365 # 1 year
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
+oauth2_scheme_optional = OAuth2PasswordBearer(tokenUrl="auth/login", auto_error=False)
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+def verify_password(plain_password, hashed_password):
+    return pwd_context.verify(plain_password, hashed_password)
+
+def get_password_hash(password):
+    return pwd_context.hash(password)
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.utcnow() + expires_delta
+    else:
+        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+    # Create a persistent session in Redis for tracking
+    sid = session_utils.create_session(data.get("id", 0))
+    to_encode.update({"exp": expire, "sid": sid})
+    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+    return encoded_jwt
+
+def decode_token(token: str):
+    try:
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        sid = payload.get("sid")
+        # Ensure session exists in Redis for stateful revocation
+        if sid and not session_utils.validate_session(sid):
+             return None
+        return payload
+    except JWTError:
+        return None

+ 30 - 0
backend/config.py

@@ -0,0 +1,30 @@
+import os
+import platform
+
+# Base Directory
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+
+# Debugging
+DEBUG = True
+
+# Slicer Settings
+# If True, triggers synchronous slicing upon file upload (slower upload, exact metrics on UI)
+SYNC_SLICING_ON_UPLOAD = True
+IS_WINDOWS = platform.system() == "Windows"
+
+if IS_WINDOWS:
+    # Default Windows path
+    SLICER_PATH = r"C:\Program Files\Prusa3D\PrusaSlicer\prusa-slicer-console.exe"
+else:
+    # Default Linux path (binary name if in PATH, or absolute path)
+    SLICER_PATH = "prusa-slicer" 
+
+# Profile configuration (can be changed per machine)
+SLICER_CONFIG = os.path.join(BASE_DIR, "printer_profile.ini")
+
+# Order settings
+UPLOAD_DIR = os.path.join(BASE_DIR, "uploads")
+PREVIEW_DIR = os.path.join(UPLOAD_DIR, "previews")
+for d in [UPLOAD_DIR, PREVIEW_DIR]:
+    if not os.path.exists(d):
+        os.makedirs(d)

+ 67 - 0
backend/db.py

@@ -0,0 +1,67 @@
+import mysql.connector
+from mysql.connector import pooling
+import os
+from typing import List, Dict, Any, Optional
+
+# Database configuration (using environment variables for production)
+DB_CONFIG = {
+    "host": os.getenv("DB_HOST", "127.0.0.1"),
+    "user": os.getenv("DB_USER", "root"),
+    "password": os.getenv("DB_PASS", ""),
+    "database": os.getenv("DB_NAME", "radionica3d"),
+    "port": int(os.getenv("DB_PORT", 3306))
+}
+
+# Connection pool
+try:
+    connection_pool = pooling.MySQLConnectionPool(
+        pool_name="radionica_pool",
+        pool_size=10,
+        pool_reset_session=True,
+        **DB_CONFIG
+    )
+    print("Database connection pool created successfully")
+except mysql.connector.Error as err:
+    print(f"Error creating connection pool: {err}")
+    connection_pool = None
+
+def get_connection():
+    """Get a connection from the pool"""
+    if connection_pool:
+        return connection_pool.get_connection()
+    return mysql.connector.connect(**DB_CONFIG)
+
+def execute_query(query: str, params: tuple = ()) -> List[Dict[str, Any]]:
+    """Execute a SELECT query and return results as a list of dictionaries"""
+    conn = get_connection()
+    cursor = conn.cursor(dictionary=True)
+    try:
+        cursor.execute(query, params)
+        result = cursor.fetchall()
+        return result
+    finally:
+        cursor.close()
+        conn.close()
+
+def execute_commit(query: str, params: tuple = ()) -> int:
+    """Execute an INSERT/UPDATE/DELETE query and return the last inserted ID"""
+    conn = get_connection()
+    cursor = conn.cursor()
+    try:
+        cursor.execute(query, params)
+        conn.commit()
+        return cursor.lastrowid
+    finally:
+        cursor.close()
+        conn.close()
+
+def execute_batch(query: str, params_list: List[tuple]):
+    """Execute multiple queries in a single transaction"""
+    conn = get_connection()
+    cursor = conn.cursor()
+    try:
+        cursor.executemany(query, params_list)
+        conn.commit()
+    finally:
+        cursor.close()
+        conn.close()

+ 42 - 0
backend/init_db.py

@@ -0,0 +1,42 @@
+import mysql.connector
+import os
+from db import DB_CONFIG
+
+def initialize_database():
+    try:
+        # Connect to MySQL (without specific database)
+        conn = mysql.connector.connect(
+            host=DB_CONFIG["host"],
+            user=DB_CONFIG["user"],
+            password=DB_CONFIG["password"],
+            port=DB_CONFIG["port"]
+        )
+        cursor = conn.cursor()
+        
+        # Read and execute schema.sql
+        schema_path = os.path.join(os.path.dirname(__file__), "schema.sql")
+        with open(schema_path, "r", encoding="utf-8") as f:
+            sql_commands = f.read().split(";")
+            
+        for command in sql_commands:
+            if command.strip():
+                try:
+                    cursor.execute(command)
+                except mysql.connector.Error as e:
+                    # Ignore table already exists errors or database creation if it exists
+                    if e.errno == 1007: # Database exists
+                        continue
+                    print(f"Executing command error: {e}")
+        
+        conn.commit()
+        print("Database initialized/updated successfully")
+        
+    except mysql.connector.Error as err:
+        print(f"Error connecting to MySQL: {err}")
+    finally:
+        if 'conn' in locals() and conn.is_connected():
+            cursor.close()
+            conn.close()
+
+if __name__ == "__main__":
+    initialize_database()

+ 50 - 0
backend/locales.py

@@ -0,0 +1,50 @@
+ERROR_TRANSLATIONS = {
+    "ru": {
+        "missing": "Это поле обязательно для заполнения",
+        "string_too_short": "Слишком коротко, минимум {min_length} символов",
+        "value_error.email": "Некорректный email",
+        "value_error.any_str.min_length": "Минимальная длина {limit_value} симв.",
+        "type_error.integer": "Должно быть целым числом",
+        "email_already_registered": "Email уже зарегистрирован",
+        "incorrect_credentials": "Неверный email или пароль",
+        "user_not_found": "Пользователь не найден",
+        "invalid_token": "Недействительный токен"
+    },
+    "me": {
+        "missing": "Ovo polje je obavezno",
+        "string_too_short": "Previše kratko, min {min_length} karaktera",
+        "value_error.email": "Neispravan email",
+        "value_error.any_str.min_length": "Minimalna dužina {limit_value} kar.",
+        "type_error.integer": "Mora biti cijeli broj",
+        "email_already_registered": "Email je već registrovan",
+        "incorrect_credentials": "Neispravan email ili lozinka",
+        "user_not_found": "Korisnik nije pronađen",
+        "invalid_token": "Neispravan token"
+    },
+    "en": {
+        "missing": "Field is required",
+        "string_too_short": "Too short, min {min_length} characters",
+        "value_error.email": "Invalid email format",
+        "email_already_registered": "Email already registered",
+        "incorrect_credentials": "Incorrect email or password",
+        "user_not_found": "User not found",
+        "invalid_token": "Invalid or expired token"
+    }
+}
+
+def translate_error(error_type: str, lang: str, **kwargs) -> str:
+    lang = lang.lower() if lang else "en"
+    if lang not in ERROR_TRANSLATIONS:
+        lang = "en"
+    
+    translations = ERROR_TRANSLATIONS[lang]
+    template = translations.get(error_type, translations.get(error_type.split('.')[-1], None))
+    
+    if not template:
+        # Fallback to English if not found in current lang
+        template = ERROR_TRANSLATIONS["en"].get(error_type, "Validation error")
+        
+    try:
+        return template.format(**kwargs)
+    except:
+        return template

+ 60 - 0
backend/main.py

@@ -0,0 +1,60 @@
+from fastapi import FastAPI, HTTPException, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.exceptions import RequestValidationError
+from fastapi.responses import JSONResponse
+import traceback
+import os
+
+import locales
+import config
+from routers import auth, orders, catalog, portfolio, files, chat
+
+app = FastAPI(title="Radionica 3D API")
+
+# Configure CORS
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+@app.exception_handler(RequestValidationError)
+async def validation_exception_handler(request: Request, exc: RequestValidationError):
+    lang = request.query_params.get("lang", "en")
+    errors = []
+    for error in exc.errors():
+        error_type = error.get("type", "unknown")
+        ctx = error.get("ctx", {})
+        translated_msg = locales.translate_error(error_type, lang, **ctx)
+        loc = ".".join(str(l) for l in error.get("loc", [])[1:])
+        errors.append({
+            "loc": error.get("loc"),
+            "msg": f"{loc}: {translated_msg}" if loc else translated_msg,
+            "type": error_type
+        })
+    return JSONResponse(status_code=422, content={"detail": errors})
+
+@app.exception_handler(Exception)
+async def all_exception_handler(request: Request, exc: Exception):
+    if config.DEBUG:
+        return JSONResponse(
+            status_code=500,
+            content={"detail": str(exc), "traceback": traceback.format_exc()}
+        )
+    return JSONResponse(status_code=500, content={"detail": "Internal server error"})
+
+# Add custom exception logging or other middleware here if needed
+
+# Include Routers
+app.include_router(auth.router)
+app.include_router(orders.router)
+app.include_router(catalog.router)
+app.include_router(portfolio.router)
+app.include_router(files.router)
+app.include_router(chat.router)
+
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8000)

+ 19 - 0
backend/notifications.py

@@ -0,0 +1,19 @@
+import logging
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger("notifications")
+
+def notify_status_change(email: str, order_id: int, new_status: str, first_name: str):
+    """
+    Hook to send notification when order status changes.
+    The USER will configure SMTP here later.
+    """
+    logger.info(f"NOTIFICATION HOOK: Sending status update to {email} for Order #{order_id}. New Status: {new_status}")
+    
+    # Template for future SMTP integration:
+    # subject = f"Radionica3D: Update on your Order #{order_id}"
+    # body = f"Hello {first_name},\n\nYour order status has been updated to: {new_status}.\n\nCheck details here: http://localhost:5173/orders"
+    # send_email(email, subject, body)
+    
+    return True

+ 45 - 0
backend/preview_utils.py

@@ -0,0 +1,45 @@
+import matplotlib.pyplot as plt
+from stl import mesh
+from mpl_toolkits import mplot3d
+import os
+
+def generate_stl_preview(stl_path: str, output_path: str):
+    """
+    Generates a PNG preview image for an STL file.
+    """
+    try:
+        # Load the STL file
+        your_mesh = mesh.Mesh.from_file(stl_path)
+
+        # Create a new plot
+        figure = plt.figure(figsize=(8, 8))
+        # Use transparent background for a premium look
+        figure.patch.set_alpha(0.0)
+        
+        axes = figure.add_subplot(111, projection='3d')
+        axes.set_facecolor((0,0,0,0)) # Transparent background inside plot
+
+        # Add the mesh to the plot
+        poly = mplot3d.art3d.Poly3DCollection(your_mesh.vectors)
+        # Professional-looking blue/gray color
+        poly.set_facecolor([0.2, 0.5, 0.8, 0.9])
+        poly.set_edgecolor([0.1, 0.1, 0.1, 0.2])
+        axes.add_collection3d(poly)
+
+        # Auto-scale the plot
+        scale = your_mesh.points.flatten()
+        axes.auto_scale_xyz(scale, scale, scale)
+        
+        # Hide axes for a clean look
+        axes.set_axis_off()
+        
+        # Adjust view for better perspective
+        axes.view_init(elev=30, azim=45)
+
+        # Save the result
+        plt.savefig(output_path, dpi=100, bbox_inches='tight', transparent=True)
+        plt.close(figure)
+        return True
+    except Exception as e:
+        print(f"Error generating preview: {e}")
+        return False

+ 12 - 0
backend/printer_profile.ini

@@ -0,0 +1,12 @@
+[printer]
+bed_shape = 0x0,400x0,400x400,0x400
+max_print_height = 400
+gcode_flavor = reprap
+nozzle_diameter = 0.4
+
+[filament]
+filament_diameter = 1.75
+extrusion_multiplier = 1
+
+[print]
+layer_height = 0.2

+ 15 - 0
backend/requirements.txt

@@ -0,0 +1,15 @@
+fastapi
+uvicorn
+mysql-connector-python
+pydantic[email]
+python-multipart
+passlib[bcrypt]
+python-jose[cryptography]
+redis
+websockets
+pytest
+pytest-asyncio
+pytest-mock
+httpx
+numpy-stl
+matplotlib

+ 40 - 0
backend/reseed.py

@@ -0,0 +1,40 @@
+import mysql.connector
+import os
+from db import DB_CONFIG
+
+def reseed_database():
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        
+        # Disable foreign key checks to truncate properly
+        cursor.execute("SET FOREIGN_KEY_CHECKS = 0;")
+        cursor.execute("TRUNCATE TABLE materials;")
+        cursor.execute("TRUNCATE TABLE services;")
+        cursor.execute("SET FOREIGN_KEY_CHECKS = 1;")
+        
+        # Re-run schema.sql seeds
+        schema_path = os.path.join(os.path.dirname(__file__), "schema.sql")
+        with open(schema_path, "r", encoding="utf-8") as f:
+            sql_commands = f.read().split(";")
+            
+        for command in sql_commands:
+            cmd = command.strip()
+            if cmd.startswith("INSERT"):
+                try:
+                    cursor.execute(cmd)
+                except mysql.connector.Error as e:
+                    print(f"Error seeding: {e}")
+        
+        conn.commit()
+        print("Database re-seeded successfully with localized keys")
+        
+    except mysql.connector.Error as err:
+        print(f"Error: {err}")
+    finally:
+        if 'conn' in locals() and conn.is_connected():
+            cursor.close()
+            conn.close()
+
+if __name__ == "__main__":
+    reseed_database()

+ 7 - 0
backend/routers/__init__.py

@@ -0,0 +1,7 @@
+# Backend routers package
+from . import auth
+from . import orders
+from . import catalog
+from . import portfolio
+from . import files
+from . import chat

+ 109 - 0
backend/routers/auth.py

@@ -0,0 +1,109 @@
+from fastapi import APIRouter, Request, Depends, HTTPException
+import auth_utils
+import db
+import schemas
+import session_utils
+import uuid
+from datetime import datetime, timedelta
+import locales
+
+router = APIRouter(prefix="/auth", tags=["auth"])
+
+@router.post("/register", response_model=schemas.UserResponse)
+async def register(request: Request, user: schemas.UserCreate, lang: str = "en"):
+    existing_user = db.execute_query("SELECT id FROM users WHERE email = %s", (user.email,))
+    if existing_user:
+        raise HTTPException(status_code=400, detail=locales.translate_error("email_already_registered", lang))
+    
+    ip_address = request.client.host if request.client else None
+    hashed_password = auth_utils.get_password_hash(user.password)
+    
+    query = """
+    INSERT INTO users (email, password_hash, first_name, last_name, phone, shipping_address, preferred_language, role, ip_address)
+    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
+    """
+    params = (user.email, hashed_password, user.first_name, user.last_name, user.phone, user.shipping_address, user.preferred_language, 'user', ip_address)
+    
+    user_id = db.execute_commit(query, params)
+    new_user = db.execute_query("SELECT id, email, first_name, last_name, phone, shipping_address, preferred_language, role, ip_address, created_at FROM users WHERE id = %s", (user_id,))
+    return new_user[0]
+
+@router.post("/login", response_model=schemas.Token)
+async def login(user_data: schemas.UserLogin, lang: str = "en"):
+    user = db.execute_query("SELECT * FROM users WHERE email = %s", (user_data.email,))
+    if not user or not auth_utils.verify_password(user_data.password, user[0]['password_hash']):
+        raise HTTPException(status_code=401, detail=locales.translate_error("incorrect_credentials", lang))
+    
+    access_token = auth_utils.create_access_token(
+        data={"sub": user[0]['email'], "id": user[0]['id'], "role": user[0]['role']}
+    )
+    return {"access_token": access_token, "token_type": "bearer"}
+
+@router.post("/social-login", response_model=schemas.Token)
+async def social_login(request: Request, data: schemas.SocialLogin):
+    user = db.execute_query("SELECT id, email, role FROM users WHERE email = %s", (data.email,))
+    if user:
+        access_token = auth_utils.create_access_token(
+            data={"sub": user[0]['email'], "id": user[0]['id'], "role": user[0]['role']}
+        )
+        return {"access_token": access_token, "token_type": "bearer"}
+    else:
+        ip_address = request.client.host if request.client else None
+        hashed_password = auth_utils.get_password_hash(str(uuid.uuid4()))
+        query = "INSERT INTO users (email, password_hash, first_name, last_name, preferred_language, role, ip_address) VALUES (%s, %s, %s, %s, %s, %s, %s)"
+        params = (data.email, hashed_password, data.first_name, data.last_name, data.preferred_language, 'user', ip_address)
+        user_id = db.execute_commit(query, params)
+        access_token = auth_utils.create_access_token(data={"sub": data.email, "id": user_id, "role": 'user'})
+        return {"access_token": access_token, "token_type": "bearer"}
+
+@router.post("/logout")
+async def logout(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if payload:
+        sid = payload.get("sid")
+        if sid: session_utils.delete_session(sid)
+    return {"message": "Successfully logged out"}
+
+@router.post("/forgot-password")
+async def forgot_password(request: schemas.ForgotPassword):
+    user = db.execute_query("SELECT id FROM users WHERE email = %s", (request.email,))
+    if not user: raise HTTPException(status_code=404, detail="Email not found")
+    token = str(uuid.uuid4())
+    expires_at = datetime.utcnow() + timedelta(minutes=15)
+    db.execute_commit("INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (%s, %s, %s)", (user[0]['id'], token, expires_at))
+    return {"message": "Reset instructions sent to your email", "demo_token": token}
+
+@router.post("/reset-password")
+async def reset_password(request: schemas.ResetPassword):
+    reset_data = db.execute_query("SELECT user_id, expires_at FROM password_reset_tokens WHERE token = %s", (request.token,))
+    if not reset_data: raise HTTPException(status_code=400, detail="Invalid token")
+    if reset_data[0]['expires_at'] < datetime.utcnow(): raise HTTPException(status_code=400, detail="Token expired")
+    hashed_password = auth_utils.get_password_hash(request.new_password)
+    db.execute_commit("UPDATE users SET password_hash = %s WHERE id = %s", (hashed_password, reset_data[0]['user_id']))
+    db.execute_commit("DELETE FROM password_reset_tokens WHERE token = %s", (request.token,))
+    return {"message": "Password reset successfully"}
+
+@router.get("/me", response_model=schemas.UserResponse)
+async def get_me(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload: raise HTTPException(status_code=401, detail="Invalid token")
+    user = db.execute_query("SELECT id, email, first_name, last_name, phone, shipping_address, preferred_language, role, ip_address, created_at FROM users WHERE id = %s", (payload.get("id"),))
+    if not user: raise HTTPException(status_code=404, detail="User not found")
+    return user[0]
+
+@router.put("/me", response_model=schemas.UserResponse)
+async def update_me(data: schemas.UserUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload: raise HTTPException(status_code=401, detail="Invalid token")
+    user_id = payload.get("id")
+    update_fields = []
+    params = []
+    for field, value in data.dict(exclude_unset=True).items():
+        update_fields.append(f"{field} = %s")
+        params.append(value)
+    if update_fields:
+        query = f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s"
+        params.append(user_id)
+        db.execute_commit(query, tuple(params))
+    user = db.execute_query("SELECT id, email, first_name, last_name, phone, shipping_address, preferred_language, role, ip_address, created_at FROM users WHERE id = %s", (user_id,))
+    return user[0]

+ 96 - 0
backend/routers/catalog.py

@@ -0,0 +1,96 @@
+from fastapi import APIRouter, Depends, HTTPException
+from typing import List
+import db
+import schemas
+import auth_utils
+
+router = APIRouter(tags=["catalog"])
+
+@router.get("/materials", response_model=List[schemas.MaterialBase])
+async def get_materials():
+    return db.execute_query("SELECT * FROM materials WHERE is_active = TRUE")
+
+@router.get("/services", response_model=List[schemas.ServiceBase])
+async def get_services():
+    return db.execute_query("SELECT id, name_key, description_key, tech_type, is_active FROM services WHERE is_active = TRUE")
+
+@router.get("/admin/materials")
+async def admin_get_materials(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    return db.execute_query("SELECT * FROM materials ORDER BY id DESC")
+
+@router.post("/admin/materials")
+async def admin_create_material(data: schemas.MaterialCreate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    query = "INSERT INTO materials (name_en, name_ru, name_me, desc_en, desc_ru, desc_me, price_per_cm3, is_active) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
+    params = (data.name_en, data.name_ru, data.name_me, data.desc_en, data.desc_ru, data.desc_me, data.price_per_cm3, data.is_active)
+    mat_id = db.execute_commit(query, params)
+    return {"id": mat_id}
+
+@router.patch("/admin/materials/{mat_id}")
+async def admin_update_material(mat_id: int, data: schemas.MaterialUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    update_fields = []
+    params = []
+    for field, value in data.dict(exclude_unset=True).items():
+        update_fields.append(f"{field} = %s")
+        params.append(value)
+    if update_fields:
+        query = f"UPDATE materials SET {', '.join(update_fields)} WHERE id = %s"
+        params.append(mat_id)
+        db.execute_commit(query, tuple(params))
+    return {"id": mat_id}
+
+@router.delete("/admin/materials/{mat_id}")
+async def admin_delete_material(mat_id: int, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    db.execute_commit("DELETE FROM materials WHERE id = %s", (mat_id,))
+    return {"id": mat_id, "status": "deleted"}
+
+@router.get("/admin/services")
+async def admin_get_services(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    return db.execute_query("SELECT * FROM services ORDER BY id DESC")
+
+@router.post("/admin/services")
+async def admin_create_service(data: schemas.ServiceCreate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    query = "INSERT INTO services (name_key, description_key, tech_type, is_active) VALUES (%s, %s, %s, %s)"
+    srv_id = db.execute_commit(query, (data.name_key, data.description_key, data.tech_type, data.is_active))
+    return {"id": srv_id}
+
+@router.patch("/admin/services/{srv_id}")
+async def admin_update_service(srv_id: int, data: schemas.ServiceUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    update_fields = []
+    params = []
+    for field, value in data.dict(exclude_unset=True).items():
+        update_fields.append(f"{field} = %s")
+        params.append(value)
+    if update_fields:
+        query = f"UPDATE services SET {', '.join(update_fields)} WHERE id = %s"
+        params.append(srv_id)
+        db.execute_commit(query, tuple(params))
+    return {"id": srv_id}
+
+@router.delete("/admin/services/{srv_id}")
+async def admin_delete_service(srv_id: int, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    db.execute_commit("DELETE FROM services WHERE id = %s", (srv_id,))
+    return {"id": srv_id, "status": "deleted"}

+ 62 - 0
backend/routers/chat.py

@@ -0,0 +1,62 @@
+from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, Query
+from services.chat_manager import manager
+import db
+import auth_utils
+import datetime
+
+router = APIRouter(tags=["chat"])
+
+@router.get("/orders/{order_id}/messages")
+async def get_order_messages(order_id: int, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload: raise HTTPException(status_code=401, detail="Invalid token")
+    role = payload.get("role")
+    user_id = payload.get("id")
+    order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
+    if not order: raise HTTPException(status_code=404, detail="Order not found")
+    if role != 'admin' and order[0]['user_id'] != user_id: raise HTTPException(status_code=403, detail="Not authorized")
+    messages = db.execute_query("SELECT id, is_from_admin, message, created_at FROM order_messages WHERE order_id = %s ORDER BY created_at ASC", (order_id,))
+    for msg in messages:
+        if msg.get('created_at'): msg['created_at'] = msg['created_at'].isoformat()
+    return messages
+
+@router.post("/orders/{order_id}/messages")
+async def post_order_message(order_id: int, request: Request, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload: raise HTTPException(status_code=401, detail="Invalid token")
+    data = await request.json()
+    message = data.get("message", "").strip()
+    if not message: raise HTTPException(status_code=400, detail="Empty message")
+    role = payload.get("role")
+    user_id = payload.get("id")
+    is_admin = (role == 'admin')
+    order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
+    if not order: raise HTTPException(status_code=404, detail="Order not found")
+    if not is_admin and order[0]['user_id'] != user_id: raise HTTPException(status_code=403, detail="Not authorized")
+    query = "INSERT INTO order_messages (order_id, user_id, is_from_admin, message) VALUES (%s, %s, %s, %s)"
+    msg_id = db.execute_commit(query, (order_id, user_id, is_admin, message))
+    now = datetime.datetime.utcnow().isoformat()
+    await manager.broadcast_to_order(order_id, {"id": msg_id, "is_from_admin": is_admin, "message": message, "created_at": now})
+    return {"id": msg_id, "status": "sent"}
+
+@router.websocket("/ws/chat/{order_id}")
+async def ws_chat(websocket: WebSocket, order_id: int, token: str = Query(...)):
+    payload = auth_utils.decode_token(token)
+    if not payload:
+        await websocket.close(code=4001)
+        return
+    role = payload.get("role")
+    user_id = payload.get("id")
+    order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
+    if not order:
+        await websocket.close(code=4004)
+        return
+    if role != 'admin' and order[0]['user_id'] != user_id:
+        await websocket.close(code=4003)
+        return
+    await manager.connect(websocket, order_id)
+    try:
+        while True:
+            await websocket.receive_text()
+    except WebSocketDisconnect:
+        manager.disconnect(websocket, order_id)

+ 50 - 0
backend/routers/files.py

@@ -0,0 +1,50 @@
+import os
+import uuid
+import hashlib
+from fastapi import APIRouter, UploadFile, File
+from typing import List
+import db
+import config
+import preview_utils
+
+router = APIRouter(prefix="/files", tags=["files"])
+
+@router.post("/upload")
+async def upload_files(files: List[UploadFile] = File(...)):
+    if not files: return {"uploaded": []}
+    uploaded_data = []
+    for file in files:
+        if not file.filename: continue
+        file_ext = os.path.splitext(file.filename)[1]
+        unique_filename = f"{uuid.uuid4()}{file_ext}"
+        file_path = os.path.join(config.UPLOAD_DIR, unique_filename).replace("\\", "/")
+        
+        sha256_hash = hashlib.sha256()
+        with open(file_path, "wb") as buffer:
+            while chunk := file.file.read(8192):
+                sha256_hash.update(chunk)
+                buffer.write(chunk)
+        
+        filament_g = None
+        print_time = None
+        if config.SYNC_SLICING_ON_UPLOAD and file_ext.lower() == ".stl":
+            import slicer_utils
+            result = slicer_utils.slice_model(file_path)
+            if result and result.get('success'):
+                filament_g = result.get('filament_g')
+                print_time = result.get('print_time_str')
+        
+        preview_path = None
+        if file_ext.lower() == ".stl":
+            preview_filename = f"{uuid.uuid4()}.png"
+            preview_path = os.path.join(config.PREVIEW_DIR, preview_filename).replace("\\", "/")
+            preview_utils.generate_stl_preview(file_path, preview_path)
+
+        query = "INSERT INTO order_files (order_id, filename, file_path, file_size, quantity, file_hash, print_time, filament_g, preview_path) VALUES (NULL, %s, %s, %s, 1, %s, %s, %s, %s)"
+        f_id = db.execute_commit(query, (file.filename, file_path, file.size, sha256_hash.hexdigest(), print_time, filament_g, preview_path))
+        
+        uploaded_data.append({
+            "id": f_id, "filename": file.filename, "size": file.size,
+            "print_time": print_time, "filament_g": filament_g, "preview_path": preview_path
+        })
+    return {"uploaded": uploaded_data}

+ 213 - 0
backend/routers/orders.py

@@ -0,0 +1,213 @@
+import db
+import schemas
+import auth_utils
+import notifications
+import config
+from typing import List, Optional
+import json
+import os
+import uuid
+import shutil
+import hashlib
+import preview_utils
+import slicer_utils
+from fastapi import APIRouter, Request, Form, Depends, HTTPException, BackgroundTasks, UploadFile, File
+from services import pricing, order_processing
+
+router = APIRouter(prefix="/orders", tags=["orders"])
+
+
+
+@router.post("")
+async def create_order(
+    request: Request,
+    background_tasks: BackgroundTasks,
+    first_name: str = Form(...),
+    last_name: str = Form(...),
+    phone: str = Form(...),
+    email: str = Form(...),
+    shipping_address: str = Form(...),
+    model_link: Optional[str] = Form(None),
+    allow_portfolio: bool = Form(False),
+    notes: Optional[str] = Form(None),
+    material_id: int = Form(...),
+    file_ids: str = Form("[]"),
+    file_quantities: str = Form("[]"),
+    quantity: int = Form(1),
+    token: str = Depends(auth_utils.oauth2_scheme_optional)
+):
+    user_id = None
+    if token:
+        payload = auth_utils.decode_token(token)
+        if payload:
+            user_id = payload.get("id")
+
+    parsed_ids = []
+    parsed_quantities = []
+    if file_ids:
+        try:
+            parsed_ids = json.loads(file_ids)
+            parsed_quantities = json.loads(file_quantities)
+        except:
+            pass
+
+    lang = request.query_params.get("lang", "en")
+    name_col = f"name_{lang}" if lang in ["en", "ru", "me"] else "name_en"
+    
+    mat_info = db.execute_query(f"SELECT {name_col}, price_per_cm3 FROM materials WHERE id = %s", (material_id,))
+    mat_name = mat_info[0][name_col] if mat_info else "Unknown"
+    mat_price = mat_info[0]['price_per_cm3'] if mat_info else 0.0
+
+    file_sizes = []
+    if parsed_ids:
+        format_strings = ','.join(['%s'] * len(parsed_ids))
+        file_rows = db.execute_query(f"SELECT file_size FROM order_files WHERE id IN ({format_strings})", tuple(parsed_ids))
+        file_sizes = [r['file_size'] for r in file_rows]
+
+    estimated_price = pricing.calculate_estimated_price(material_id, file_sizes, parsed_quantities if parsed_quantities else None)
+
+    order_query = """
+    INSERT INTO orders (user_id, first_name, last_name, phone, email, shipping_address, model_link, status, allow_portfolio, estimated_price, material_name, material_price, quantity, notes)
+    VALUES (%s, %s, %s, %s, %s, %s, %s, 'pending', %s, %s, %s, %s, %s, %s)
+    """
+    order_params = (user_id, first_name, last_name, phone, email, shipping_address, model_link, allow_portfolio, estimated_price, mat_name, mat_price, quantity, notes)
+    
+    try:
+        order_insert_id = db.execute_commit(order_query, order_params)
+        if parsed_ids:
+            for idx, f_id in enumerate(parsed_ids):
+                qty = parsed_quantities[idx] if idx < len(parsed_quantities) else 1
+                db.execute_commit("UPDATE order_files SET order_id = %s, quantity = %s WHERE id = %s", (order_insert_id, qty, f_id))
+        background_tasks.add_task(order_processing.process_order_slicing, order_insert_id)
+        return {"status": "success", "order_id": order_insert_id, "message": "Order submitted successfully"}
+    except Exception as e:
+        print(f"Error creating order: {e}")
+        raise HTTPException(status_code=500, detail="Internal server error occurred while processing order")
+
+@router.get("/my")
+async def get_my_orders(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload:
+        raise HTTPException(status_code=401, detail="Invalid token")
+    
+    user_id = payload.get("id")
+    query = """
+    SELECT o.*, 
+           GROUP_CONCAT(JSON_OBJECT('filename', f.filename, 'file_path', f.file_path, 'quantity', f.quantity, 'preview_path', f.preview_path, 'print_time', f.print_time, 'filament_g', f.filament_g)) as files
+    FROM orders o
+    LEFT JOIN order_files f ON o.id = f.order_id
+    WHERE o.user_id = %s
+    GROUP BY o.id
+    ORDER BY o.created_at DESC
+    """
+    results = db.execute_query(query, (user_id,))
+    for row in results:
+        if row['files']:
+            try: row['files'] = json.loads(f"[{row['files']}]")
+            except: row['files'] = []
+        else: row['files'] = []
+    return results
+
+@router.post("/estimate")
+async def get_price_estimate(data: schemas.EstimateRequest):
+    material = db.execute_query("SELECT price_per_cm3 FROM materials WHERE id = %s", (data.material_id,))
+    price_per_cm3 = float(material[0]['price_per_cm3']) if material else 0.0
+    
+    file_prices = []
+    base_fee = 5.0
+    for size in data.file_sizes:
+        total_size_mb = size / (1024 * 1024)
+        estimated_volume = total_size_mb * 8.0 
+        file_cost = base_fee + (estimated_volume * price_per_cm3)
+        file_prices.append(round(file_cost, 2))
+        
+    qts = data.file_quantities if data.file_quantities else [1]*len(data.file_sizes)
+    return {"file_prices": file_prices, "total_estimate": round(sum(p * q for p, q in zip(file_prices, qts)), 2)}
+
+# --- ADMIN ORDER ENDPOINTS ---
+
+@router.get("/admin/list") # Using /admin/list to avoid conflict with /my
+async def get_admin_orders(token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    
+    query = """
+    SELECT o.*, 
+           GROUP_CONCAT(JSON_OBJECT('filename', f.filename, 'file_path', f.file_path, 'file_size', f.file_size, 'quantity', f.quantity, 'preview_path', f.preview_path, 'print_time', f.print_time, 'filament_g', f.filament_g)) as files
+    FROM orders o
+    LEFT JOIN order_files f ON o.id = f.order_id
+    GROUP BY o.id
+    ORDER BY o.created_at DESC
+    """
+    results = db.execute_query(query)
+    for row in results:
+        if row['files']:
+            try: row['files'] = json.loads(f"[{row['files']}]")
+            except: row['files'] = []
+        else: row['files'] = []
+        photos = db.execute_query("SELECT id, file_path, is_public FROM order_photos WHERE order_id = %s", (row['id'],))
+        row['photos'] = photos
+    return results
+
+@router.patch("/{order_id}/admin")
+async def update_order_admin(order_id: int, data: schemas.AdminOrderUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    
+    update_fields = []
+    params = []
+    if data.status:
+        order_info = db.execute_query("SELECT email, first_name FROM orders WHERE id = %s", (order_id,))
+        if order_info:
+            notifications.notify_status_change(order_info[0]['email'], order_id, data.status, order_info[0]['first_name'])
+        update_fields.append("status = %s")
+        params.append(data.status)
+    if data.total_price is not None:
+        update_fields.append("total_price = %s")
+        params.append(data.total_price)
+    
+    if update_fields:
+        query = f"UPDATE orders SET {', '.join(update_fields)} WHERE id = %s"
+        params.append(order_id)
+        db.execute_commit(query, tuple(params))
+    return {"id": order_id, "status": "updated"}
+
+@router.post("/{order_id}/attach-file")
+async def admin_attach_file(
+    order_id: int,
+    file: UploadFile = File(...),
+    token: str = Depends(auth_utils.oauth2_scheme)
+):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+        
+    unique_filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
+    file_path = os.path.join(config.UPLOAD_DIR, unique_filename).replace("\\", "/")
+    
+    sha256_hash = hashlib.sha256()
+    with open(file_path, "wb") as buffer:
+        while chunk := file.file.read(8192):
+            sha256_hash.update(chunk)
+            buffer.write(chunk)
+    
+    preview_path = None
+    if file_path.lower().endswith(".stl"):
+        preview_filename = f"{uuid.uuid4()}.png"
+        preview_path = os.path.join(config.PREVIEW_DIR, preview_filename).replace("\\", "/")
+        preview_utils.generate_stl_preview(file_path, preview_path)
+
+    filament_g = None
+    print_time = None
+    if file_path.lower().endswith(".stl"):
+        result = slicer_utils.slice_model(file_path)
+        if result and result.get('success'):
+            filament_g = result.get('filament_g')
+            print_time = result.get('print_time_str')
+
+    query = "INSERT INTO order_files (order_id, filename, file_path, file_size, quantity, file_hash, print_time, filament_g, preview_path) VALUES (%s, %s, %s, %s, 1, %s, %s, %s, %s)"
+    f_id = db.execute_commit(query, (order_id, file.filename, file_path, file.size, sha256_hash.hexdigest(), print_time, filament_g, preview_path))
+    
+    return {"id": f_id, "filename": file.filename, "preview_path": preview_path, "filament_g": filament_g, "print_time": print_time}

+ 57 - 0
backend/routers/portfolio.py

@@ -0,0 +1,57 @@
+from fastapi import APIRouter, Depends, HTTPException, Form, UploadFile, File
+import db
+import schemas
+import auth_utils
+import config
+import os
+import uuid
+import shutil
+
+router = APIRouter(tags=["portfolio"])
+
+@router.get("/portfolio")
+async def get_public_portfolio():
+    query = """
+    SELECT p.id, p.file_path, o.material_name, o.id as order_id
+    FROM order_photos p
+    JOIN orders o ON p.order_id = o.id
+    WHERE p.is_public = TRUE AND o.allow_portfolio = TRUE
+    ORDER BY p.created_at DESC
+    """
+    return db.execute_query(query)
+
+@router.post("/admin/orders/{order_id}/photos")
+async def admin_upload_order_photo(
+    order_id: int, 
+    is_public: bool = Form(False),
+    file: UploadFile = File(...), 
+    token: str = Depends(auth_utils.oauth2_scheme)
+):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    order = db.execute_query("SELECT allow_portfolio FROM orders WHERE id = %s", (order_id,))
+    if not order: raise HTTPException(status_code=404, detail="Order not found")
+    if is_public and not order[0]['allow_portfolio']:
+         raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage")
+    if not file.filename: raise HTTPException(status_code=400, detail="Invalid file")
+    unique_filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
+    file_path = os.path.join(config.UPLOAD_DIR, unique_filename).replace("\\", "/")
+    with open(file_path, "wb") as buffer:
+        shutil.copyfileobj(file.file, buffer)
+    query = "INSERT INTO order_photos (order_id, file_path, is_public) VALUES (%s, %s, %s)"
+    photo_id = db.execute_commit(query, (order_id, file_path, is_public))
+    return {"id": photo_id, "file_path": file_path, "is_public": is_public}
+
+@router.patch("/admin/photos/{photo_id}")
+async def admin_update_photo_status(photo_id: int, data: schemas.PhotoUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
+    payload = auth_utils.decode_token(token)
+    if not payload or payload.get("role") != 'admin':
+        raise HTTPException(status_code=403, detail="Admin role required")
+    query = "SELECT p.*, o.allow_portfolio FROM order_photos p JOIN orders o ON p.order_id = o.id WHERE p.id = %s"
+    photo_data = db.execute_query(query, (photo_id,))
+    if not photo_data: raise HTTPException(status_code=404, detail="Photo not found")
+    if data.is_public and not photo_data[0]['allow_portfolio']:
+        raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage")
+    db.execute_commit("UPDATE order_photos SET is_public = %s WHERE id = %s", (data.is_public, photo_id))
+    return {"id": photo_id, "is_public": data.is_public}

+ 102 - 0
backend/schema.sql

@@ -0,0 +1,102 @@
+CREATE DATABASE IF NOT EXISTS radionica3d;
+USE radionica3d;
+
+-- Materials table
+CREATE TABLE IF NOT EXISTS materials (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    name_key VARCHAR(100) NOT NULL,
+    description_key VARCHAR(255),
+    price_per_cm3 DECIMAL(10, 2) DEFAULT 0.00,
+    is_active BOOLEAN DEFAULT TRUE,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Services table
+CREATE TABLE IF NOT EXISTS services (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    name_key VARCHAR(100) NOT NULL,
+    description_key VARCHAR(255),
+    tech_type VARCHAR(50), -- e.g., FDM, SLA, SLS
+    is_active BOOLEAN DEFAULT TRUE,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Users table
+CREATE TABLE IF NOT EXISTS users (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    email VARCHAR(150) NOT NULL UNIQUE,
+    password_hash VARCHAR(255) NOT NULL,
+    first_name VARCHAR(100),
+    last_name VARCHAR(100),
+    phone VARCHAR(20),
+    shipping_address TEXT,
+    role VARCHAR(20) DEFAULT 'user', -- user, admin
+    ip_address VARCHAR(45), -- Supports IPv6
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+-- Password reset tokens
+CREATE TABLE IF NOT EXISTS password_reset_tokens (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    user_id INT NOT NULL,
+    token VARCHAR(255) NOT NULL UNIQUE,
+    expires_at TIMESTAMP NOT NULL,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
+
+-- Orders table
+CREATE TABLE IF NOT EXISTS orders (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    user_id INT NULL,
+    first_name VARCHAR(100) NOT NULL,
+    last_name VARCHAR(100) NOT NULL,
+    phone VARCHAR(20) NOT NULL,
+    email VARCHAR(150) NOT NULL,
+    shipping_address TEXT NOT NULL,
+    model_link TEXT,
+    status VARCHAR(50) DEFAULT 'pending', -- pending, processing, shipped, completed, cancelled
+    total_price DECIMAL(10, 2) DEFAULT NULL, -- To be filled by admin later
+    estimated_price DECIMAL(10, 2) DEFAULT NULL,
+    material_name VARCHAR(100) NULL, -- Factual Snapshot
+    material_price DECIMAL(10, 4) DEFAULT NULL, -- Factual Snapshot
+    allow_portfolio BOOLEAN DEFAULT FALSE,
+    quantity INT DEFAULT 1,
+    notes TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+);
+
+-- Order Files table
+CREATE TABLE IF NOT EXISTS order_files (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    order_id INT NOT NULL,
+    filename VARCHAR(255) NOT NULL,
+    file_path VARCHAR(512) NOT NULL,
+    file_size INT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
+);
+
+-- Order Photos table (for reports and portfolio)
+CREATE TABLE IF NOT EXISTS order_photos (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    order_id INT NOT NULL,
+    file_path VARCHAR(512) NOT NULL,
+    is_public BOOLEAN DEFAULT FALSE, -- Only if allow_portfolio on order is true
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
+);
+
+-- Initial Data Migration (Optional, can be done via init script)
+INSERT INTO materials (name_key, description_key, price_per_cm3) VALUES 
+('pricing.matNames.pla', 'pricing.matDescs.pla', 0.04),
+('pricing.matNames.abs', 'pricing.matDescs.abs', 0.05),
+('pricing.matNames.petg', 'pricing.matDescs.petg', 0.06),
+('pricing.matNames.resin', 'pricing.matDescs.resin', 0.12);
+
+INSERT INTO services (name_key, description_key, tech_type) VALUES 
+('services.fdm.title', 'services.fdm.description', 'FDM'),
+('services.sla.title', 'services.sla.description', 'SLA');

+ 152 - 0
backend/schemas.py

@@ -0,0 +1,152 @@
+from pydantic import BaseModel, EmailStr, Field
+from typing import Optional, List
+from datetime import datetime
+
+# Response models
+class MaterialBase(BaseModel):
+    id: int
+    name_en: Optional[str] = None
+    name_ru: Optional[str] = None
+    name_me: Optional[str] = None
+    desc_en: Optional[str] = None
+    desc_ru: Optional[str] = None
+    desc_me: Optional[str] = None
+    name_key: Optional[str] = None
+    description_key: Optional[str] = None
+    price_per_cm3: float
+    is_active: bool
+
+class ServiceBase(BaseModel):
+    id: int
+    name_key: str
+    description_key: Optional[str]
+    tech_type: Optional[str]
+    is_active: bool
+
+# Management models
+class MaterialCreate(BaseModel):
+    name_en: str
+    name_ru: str
+    name_me: str
+    desc_en: str
+    desc_ru: str
+    desc_me: str
+    price_per_cm3: float
+    is_active: bool = True
+
+class MaterialUpdate(BaseModel):
+    name_en: Optional[str] = None
+    name_ru: Optional[str] = None
+    name_me: Optional[str] = None
+    desc_en: Optional[str] = None
+    desc_ru: Optional[str] = None
+    desc_me: Optional[str] = None
+    price_per_cm3: Optional[float] = None
+    is_active: Optional[bool] = None
+
+class ServiceCreate(BaseModel):
+    name_key: str
+    description_key: Optional[str] = None
+    tech_type: Optional[str] = None
+    is_active: bool = True
+
+class ServiceUpdate(BaseModel):
+    name_key: Optional[str] = None
+    description_key: Optional[str] = None
+    tech_type: Optional[str] = None
+    is_active: Optional[bool] = None
+
+# Order creation models
+class OrderFileBase(BaseModel):
+    filename: str
+    file_size: Optional[int]
+
+class OrderCreate(BaseModel):
+    first_name: str = Field(..., min_length=1)
+    last_name: str = Field(..., min_length=1)
+    phone: str = Field(..., min_length=5)
+    email: EmailStr
+    shipping_address: str = Field(..., min_length=10)
+    model_link: Optional[str] = None
+    quantity: int = Field(1, ge=1)
+    notes: Optional[str] = None
+
+# User models
+class UserCreate(BaseModel):
+    email: EmailStr
+    password: str = Field(..., min_length=6)
+    first_name: Optional[str] = None
+    last_name: Optional[str] = None
+    phone: Optional[str] = None
+    shipping_address: Optional[str] = None
+    preferred_language: Optional[str] = "en"
+
+class UserUpdate(BaseModel):
+    first_name: Optional[str] = None
+    last_name: Optional[str] = None
+    phone: Optional[str] = None
+    shipping_address: Optional[str] = None
+    preferred_language: Optional[str] = None
+
+class UserLogin(BaseModel):
+    email: EmailStr
+    password: str
+
+class UserResponse(BaseModel):
+    id: int
+    email: EmailStr
+    first_name: Optional[str] = None
+    last_name: Optional[str] = None
+    phone: Optional[str] = None
+    shipping_address: Optional[str] = None
+    preferred_language: str = "en"
+    role: str
+    ip_address: Optional[str] = None
+    created_at: datetime
+    
+    class Config:
+        from_attributes = True
+
+class AdminOrderUpdate(BaseModel):
+    status: Optional[str] = None
+    total_price: Optional[float] = None
+
+class EstimateRequest(BaseModel):
+    material_id: int
+    file_sizes: List[int] # in bytes
+    file_quantities: Optional[List[int]] = None
+    # Possible to add density or other params later
+
+class Token(BaseModel):
+    access_token: str
+    token_type: str
+
+class SocialLogin(BaseModel):
+    provider: str # 'google' or 'facebook'
+    token: str
+    email: EmailStr
+    first_name: Optional[str] = None
+    last_name: Optional[str] = None
+    preferred_language: Optional[str] = "en"
+
+class ForgotPassword(BaseModel):
+    email: EmailStr
+
+class ResetPassword(BaseModel):
+    token: str
+    new_password: str = Field(..., min_length=6)
+
+class PhotoUpdate(BaseModel):
+    is_public: bool
+
+class OrderResponse(OrderCreate):
+    id: int
+    status: str
+    total_price: Optional[float] = None
+    estimated_price: Optional[float] = None
+    material_name: Optional[str] = None
+    material_price: Optional[float] = None
+    created_at: datetime
+    
+    class Config:
+        from_attributes = True

+ 1 - 0
backend/services/__init__.py

@@ -0,0 +1 @@
+# Backend services package

+ 39 - 0
backend/services/chat_manager.py

@@ -0,0 +1,39 @@
+import json
+import datetime
+import db
+from typing import Dict, List, Any
+from fastapi import WebSocket
+
+class ChatConnectionManager:
+    def __init__(self):
+        # order_id -> list of active websockets
+        self.active_connections: Dict[int, List[WebSocket]] = {}
+
+    async def connect(self, websocket: WebSocket, order_id: int):
+        await websocket.accept()
+        if order_id not in self.active_connections:
+            self.active_connections[order_id] = []
+        self.active_connections[order_id].append(websocket)
+
+    def disconnect(self, websocket: WebSocket, order_id: int):
+        if order_id in self.active_connections:
+            self.active_connections[order_id].remove(websocket)
+            if not self.active_connections[order_id]:
+                del self.active_connections[order_id]
+
+    async def broadcast_to_order(self, order_id: int, message: Any):
+        if order_id in self.active_connections:
+            # We must serialize datetime to JSON string
+            if isinstance(message, dict) and 'created_at' in message:
+                if isinstance(message['created_at'], datetime.datetime):
+                    message['created_at'] = message['created_at'].isoformat()
+            
+            payload = json.dumps(message)
+            for connection in self.active_connections[order_id]:
+                try:
+                    await connection.send_text(payload)
+                except:
+                    # Connection might be dead
+                    pass
+
+manager = ChatConnectionManager()

+ 27 - 0
backend/services/order_processing.py

@@ -0,0 +1,27 @@
+import db
+import slicer_utils
+import config
+import os
+from typing import List
+
+def process_order_slicing(order_id: int):
+    """
+    Background task to compute accurate print time and filament usage for all files in an order.
+    """
+    files = db.execute_query("SELECT id, file_path FROM order_files WHERE order_id = %s", (order_id,))
+    if not files:
+        return
+
+    for f in files:
+        file_path = f['file_path']
+        if not os.path.exists(file_path):
+            continue
+            
+        file_ext = os.path.splitext(file_path)[1].lower()
+        if file_ext == '.stl':
+            result = slicer_utils.slice_model(file_path)
+            if result and result.get('success'):
+                db.execute_commit(
+                    "UPDATE order_files SET print_time = %s, filament_g = %s WHERE id = %s",
+                    (result.get('print_time_str'), result.get('filament_g'), f['id'])
+                )

+ 27 - 0
backend/services/pricing.py

@@ -0,0 +1,27 @@
+from typing import List
+import db
+
+def calculate_estimated_price(material_id: int, file_sizes: List[int], file_quantities: List[int] = None) -> float:
+    """
+    Internal logic to estimate price per file based on file size (proxy for volume).
+    """
+    material = db.execute_query("SELECT price_per_cm3 FROM materials WHERE id = %s", (material_id,))
+    if not material:
+        return 0.0
+    
+    price_per_cm3 = float(material[0]['price_per_cm3'])
+    
+    if file_quantities is None or len(file_quantities) != len(file_sizes):
+        file_quantities = [1] * len(file_sizes)
+        
+    estimated_total = 0.0
+    base_fee = 5.0 # Minimum setup fee per file
+    
+    for size, qty in zip(file_sizes, file_quantities):
+        total_size_mb = size / (1024 * 1024)
+        # Empirical conversion: ~8cm3 per 1MB of STL (binary)
+        estimated_volume = total_size_mb * 8.0 
+        file_cost = base_fee + (estimated_volume * price_per_cm3)
+        estimated_total += file_cost * qty
+    
+    return round(estimated_total, 2)

+ 30 - 0
backend/session_utils.py

@@ -0,0 +1,30 @@
+import redis
+import os
+import uuid
+from datetime import timedelta
+
+# Redis Configuration (Environment variables for production)
+REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
+REDIS_PORT = int(os.getenv("REDIS_PORT", 6379))
+REDIS_DB = int(os.getenv("REDIS_DB", 0))
+
+r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True)
+
+def create_session(user_id: int, expires_days: int = 365) -> str:
+    """Create a unique session ID in Redis and map it to user_id"""
+    session_id = str(uuid.uuid4())
+    # Save the session with an expiration time
+    r.setex(f"session:{session_id}", timedelta(days=expires_days), str(user_id))
+    return session_id
+
+def validate_session(session_id: str) -> bool:
+    """Check if the session ID exists in Redis"""
+    return r.exists(f"session:{session_id}") == 1
+
+def delete_session(session_id: str):
+    """Delete a session from Redis (Logout)"""
+    r.delete(f"session:{session_id}")
+
+def get_user_id_from_session(session_id: str):
+    """Retrieve the user_id associated with a session"""
+    return r.get(f"session:{session_id}")

+ 66 - 0
backend/slicer_utils.py

@@ -0,0 +1,66 @@
+import subprocess
+import os
+import re
+import logging
+import shutil
+from config import SLICER_PATH, SLICER_CONFIG
+
+logger = logging.getLogger(__name__)
+
+def slice_model(file_path: str):
+    """
+    Runs PrusaSlicer CLI to extract the EXACT 3D volume of the STL/OBJ file using --info.
+    Returns a dict with estimated print_time (str) and filament_used (g).
+    """
+    if not shutil.which(SLICER_PATH):
+        logger.error(f"Slicer not found at {SLICER_PATH}")
+        return None
+    
+    cmd = [
+        SLICER_PATH,
+        "--info",
+        file_path
+    ]
+
+    try:
+        # Run info extraction (very fast, no full G-code generation)
+        process = subprocess.run(cmd, capture_output=True, text=True, check=True)
+        content = process.stdout
+        
+        # Regex to find volume in mm3
+        # Format: volume = 87552.437500
+        volume_match = re.search(r"volume\s*=\s*([\d\.]+)", content)
+        if not volume_match:
+            logger.error("Volume could not be parsed from PrusaSlicer output.")
+            return None
+            
+        volume_mm3 = float(volume_match.group(1))
+        volume_cm3 = volume_mm3 / 1000.0
+        
+        # Estimate weight assuming average density of PLA/PETG (~1.25 g/cm3)
+        filament_g = round(volume_cm3 * 1.25, 2)
+        
+        # Estimate printing time assuming average volumetric flow rate of ~10 mm3/s
+        # (This is a very realistic average for standard 0.4mm nozzle including travel/supports)
+        time_seconds = int(volume_mm3 / 10.0)
+        
+        hours = time_seconds // 3600
+        minutes = (time_seconds % 3600) // 60
+        
+        if hours > 0:
+            time_str = f"{hours}h {minutes}m"
+        else:
+            time_str = f"{minutes}m"
+            
+        return {
+            "filament_g": filament_g,
+            "print_time_str": time_str,
+            "success": True
+        }
+        
+    except subprocess.CalledProcessError as e:
+        logger.error(f"Slicing info error: {e.stderr}")
+        return None
+    except Exception as e:
+        logger.error(f"General error in slice_model: {e}")
+        return None

+ 1 - 0
backend/tests/__init__.py

@@ -0,0 +1 @@
+# Backend tests package

+ 33 - 0
backend/tests/conftest.py

@@ -0,0 +1,33 @@
+import pytest
+import sys
+import os
+from unittest.mock import MagicMock
+from fastapi.testclient import TestClient
+
+# Add the parent directory to sys.path so we can import internal modules
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+# Mock the database and session modules BEFORE importing the app
+mock_db = MagicMock()
+mock_session = MagicMock()
+sys.modules["db"] = mock_db
+sys.modules["backend.db"] = mock_db
+sys.modules["session_utils"] = mock_session
+sys.modules["backend.session_utils"] = mock_session
+
+# Configuration to bypass real Redis
+mock_session.create_session.return_value = "mock-session-id"
+mock_session.validate_session.return_value = True
+
+from main import app
+
+@pytest.fixture
+def client():
+    """Test client for FastAPI app"""
+    with TestClient(app) as c:
+        yield c
+
+@pytest.fixture
+def db_mock():
+    """Fixture to access the mocked db module"""
+    return mock_db

+ 46 - 0
backend/tests/test_auth.py

@@ -0,0 +1,46 @@
+import pytest
+from unittest.mock import MagicMock
+
+def test_register_duplicate_email(client, db_mock):
+    # Setup mock return value
+    db_mock.execute_query.return_value = [{"id": 1}]
+    
+    response = client.post(
+        "/auth/register",
+        json={
+            "email": "test@example.com",
+            "password": "password123",
+            "first_name": "Test",
+            "last_name": "User"
+        }
+    )
+    
+    assert response.status_code == 400
+    assert "email_already_registered" in response.json()["detail"] or "already registered" in response.json()["detail"].lower()
+
+import auth_utils
+
+def test_login_success(client, db_mock):
+    # Mock user entry
+    db_mock.execute_query.return_value = [{
+        "id": 1,
+        "email": "test@example.com",
+        "password_hash": auth_utils.get_password_hash("password123"),
+        "role": "user"
+    }]
+    
+    response = client.post(
+        "/auth/login",
+        json={
+            "email": "test@example.com",
+            "password": "password123"
+        }
+    )
+    
+    assert response.status_code == 200
+    assert "access_token" in response.json()
+    assert response.json()["token_type"] == "bearer"
+
+def test_get_me_unauthorized(client):
+    response = client.get("/auth/me")
+    assert response.status_code == 401

+ 16 - 0
backend/tests/test_catalog.py

@@ -0,0 +1,16 @@
+import pytest
+
+def test_get_materials(client, db_mock):
+    db_mock.execute_query.return_value = [
+        {"id": 1, "name_en": "PLA", "desc_en": "Standard", "price_per_cm3": 0.05, "is_active": True}
+    ]
+    
+    response = client.get("/materials")
+    assert response.status_code == 200
+    assert len(response.json()) == 1
+    assert response.json()[0]["name_en"] == "PLA"
+
+def test_admin_get_materials_forbidden(client):
+    # Testing RBAC without token
+    response = client.get("/admin/materials")
+    assert response.status_code == 401 # Should actually be 401 if no token provided at all

+ 52 - 0
backend/tests/test_orders.py

@@ -0,0 +1,52 @@
+import pytest
+from unittest.mock import MagicMock
+
+def test_create_order_authorized(client, db_mock, mocker):
+    # Mock background tasks to avoid side effects
+    mocker.patch("fastapi.BackgroundTasks.add_task")
+    
+    db_mock.execute_query.side_effect = None
+    db_mock.execute_query.return_value = [{"id": 1, "name_en": "PLA", "price_per_cm3": 0.05}]
+    db_mock.execute_commit.return_value = 123
+    
+    import auth_utils
+    token = auth_utils.create_access_token({"sub": "test@example.com", "id": 1, "role": "user"})
+    
+    response = client.post(
+        "/orders",
+        headers={"Authorization": f"Bearer {token}"},
+        data={
+            "first_name": "Test",
+            "last_name": "User",
+            "phone": "12345678",
+            "email": "test@example.com",
+            "shipping_address": "123 Test St",
+            "material_id": 1,
+            "quantity": 2
+        }
+    )
+    
+    assert response.status_code == 200
+    assert response.json()["order_id"] == 123
+
+def test_create_order_no_material_defaults(client, db_mock, mocker):
+    mocker.patch("fastapi.BackgroundTasks.add_task")
+    db_mock.execute_query.side_effect = None
+    db_mock.execute_query.return_value = [] # Material not found
+    
+    response = client.post(
+        "/orders",
+        data={
+            "first_name": "Test",
+            "last_name": "User",
+            "phone": "12345678",
+            "email": "test@example.com",
+            "shipping_address": "123 Test St",
+            "material_id": 999,
+            "quantity": 1
+        }
+    )
+    
+    # The current implementation defaults to "Unknown" material instead of 404
+    assert response.status_code == 200
+    assert response.json()["status"] == "success"

+ 20 - 0
build_frontend.sh

@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Скрипт для чистой сборки фронтенда для production
+
+echo "🚀 Начинаем сборку Radionica3D Frontend (Production)..."
+
+# 1. Устанавливаем зависимости чисто и надежно
+echo "📦 Установка NPM-зависимостей..."
+npm ci
+
+# 2. Очищаем старую сборку (если есть) на всякий случай
+echo "🧹 Очистка старых данных..."
+rm -rf dist
+
+# 3. Запускаем сборку Vue
+echo "🔨 Сборка проекта (Vite / Vue 3)..."
+npm run build
+
+echo "✅ Готово! Продакшен-файлы лежат в директории ./dist"
+echo "Теперь вы можете скопировать папку ./dist на ваш Nginx или другой веб-сервер."

+ 35 - 0
fix_users_table.py

@@ -0,0 +1,35 @@
+import mysql.connector
+from db import DB_CONFIG
+
+def fix_users_table():
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        
+        columns_to_add = [
+            ("first_name", "VARCHAR(100)"),
+            ("last_name", "VARCHAR(100)"),
+            ("phone", "VARCHAR(20)"),
+            ("shipping_address", "TEXT")
+        ]
+        
+        for name, type in columns_to_add:
+            try:
+                cursor.execute(f"ALTER TABLE users ADD COLUMN {name} {type};")
+                print(f"Added column {name}")
+            except mysql.connector.Error as e:
+                if e.errno == 1060: # Column already exists
+                    print(f"Column {name} already exists")
+                else:
+                    print(f"Error adding {name}: {e}")
+                    
+        conn.commit()
+    except mysql.connector.Error as err:
+        print(f"Error: {err}")
+    finally:
+        if 'conn' in locals() and conn.is_connected():
+            cursor.close()
+            conn.close()
+
+if __name__ == "__main__":
+    fix_users_table()

+ 14 - 0
index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Radionica 3D | Modern 3D Printing Services</title>
+    <meta name="description" content="Professional 3D printing and rapid prototyping services with instant quotes and high-quality materials." />
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 3321 - 0
package-lock.json

@@ -0,0 +1,3321 @@
+{
+  "name": "radionica3d",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "radionica3d",
+      "version": "0.1.0",
+      "dependencies": {
+        "@tanstack/vue-query": "^5.25.0",
+        "@vueuse/core": "^10.9.0",
+        "@vueuse/motion": "^2.1.0",
+        "class-variance-authority": "^0.7.0",
+        "clsx": "^2.1.0",
+        "date-fns": "^3.3.1",
+        "i18next-browser-languagedetector": "^8.2.1",
+        "lucide-vue-next": "^0.354.0",
+        "pinia": "^2.1.7",
+        "tailwind-merge": "^2.2.1",
+        "tailwindcss-animate": "^1.0.7",
+        "vue": "^3.4.21",
+        "vue-i18n": "^9.13.1",
+        "vue-router": "^4.3.0",
+        "vue-sonner": "^1.1.4",
+        "zod": "^3.22.4"
+      },
+      "devDependencies": {
+        "@types/node": "^20.11.25",
+        "@vitejs/plugin-vue": "^5.0.4",
+        "autoprefixer": "^10.4.18",
+        "postcss": "^8.4.35",
+        "tailwindcss": "^3.4.1",
+        "typescript": "^5.3.3",
+        "vite": "^5.1.5",
+        "vue-tsc": "^2.0.7"
+      }
+    },
+    "node_modules/@alloc/quick-lru": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+      "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+      "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+      "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+      "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+      "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
+      "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
+      "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
+      "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
+      "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
+      "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
+      "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
+      "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
+      "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
+      "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
+      "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
+      "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
+      "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
+      "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
+      "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
+      "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
+      "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
+      "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
+      "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@intlify/core-base": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+      "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-compiler": "9.14.5",
+        "@intlify/shared": "9.14.5"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/message-compiler": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+      "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/shared": "9.14.5",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/shared": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+      "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nuxt/kit": {
+      "version": "3.21.2",
+      "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.2.tgz",
+      "integrity": "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "c12": "^3.3.3",
+        "consola": "^3.4.2",
+        "defu": "^6.1.4",
+        "destr": "^2.0.5",
+        "errx": "^0.1.0",
+        "exsolve": "^1.0.8",
+        "ignore": "^7.0.5",
+        "jiti": "^2.6.1",
+        "klona": "^2.0.6",
+        "knitwork": "^1.3.0",
+        "mlly": "^1.8.1",
+        "ohash": "^2.0.11",
+        "pathe": "^2.0.3",
+        "pkg-types": "^2.3.0",
+        "rc9": "^3.0.0",
+        "scule": "^1.3.0",
+        "semver": "^7.7.4",
+        "tinyglobby": "^0.2.15",
+        "ufo": "^1.6.3",
+        "unctx": "^2.5.0",
+        "untyped": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/@nuxt/kit/node_modules/jiti": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+      "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/@nuxt/kit/node_modules/semver": {
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+      "license": "ISC",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
+      "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
+      "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
+      "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
+      "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
+      "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
+      "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
+      "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
+      "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
+      "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
+      "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
+      "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
+      "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
+      "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
+      "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
+      "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
+      "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
+      "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
+      "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
+      "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
+      "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
+      "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
+      "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
+      "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
+      "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
+      "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@tanstack/match-sorter-utils": {
+      "version": "8.19.4",
+      "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz",
+      "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==",
+      "license": "MIT",
+      "dependencies": {
+        "remove-accents": "0.5.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@tanstack/vue-query": {
+      "version": "5.96.2",
+      "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.96.2.tgz",
+      "integrity": "sha512-mDJoLG1kElNu0vFf8m+sdzka15lWXUZ3CXYVOuivHVdrbjjs5fI0o8i1iqAtIGGL5q+sek4XTbPeWW327zOgtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@tanstack/match-sorter-utils": "^8.19.4",
+        "@tanstack/query-core": "5.96.2",
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.2",
+        "vue": "^2.6.0 || ^3.3.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@tanstack/vue-query/node_modules/@tanstack/query-core": {
+      "version": "5.96.2",
+      "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.2.tgz",
+      "integrity": "sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "20.19.37",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
+      "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.20",
+      "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+      "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.4",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+      "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@volar/language-core": {
+      "version": "2.4.15",
+      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
+      "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/source-map": "2.4.15"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "2.4.15",
+      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz",
+      "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@volar/typescript": {
+      "version": "2.4.15",
+      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz",
+      "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "2.4.15",
+        "path-browserify": "^1.0.1",
+        "vscode-uri": "^3.0.8"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz",
+      "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.2",
+        "@vue/shared": "3.5.32",
+        "entities": "^7.0.1",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz",
+      "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.32",
+        "@vue/shared": "3.5.32"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz",
+      "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.2",
+        "@vue/compiler-core": "3.5.32",
+        "@vue/compiler-dom": "3.5.32",
+        "@vue/compiler-ssr": "3.5.32",
+        "@vue/shared": "3.5.32",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.21",
+        "postcss": "^8.5.8",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz",
+      "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.32",
+        "@vue/shared": "3.5.32"
+      }
+    },
+    "node_modules/@vue/compiler-vue2": {
+      "version": "2.7.16",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+      "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "de-indent": "^1.0.2",
+        "he": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
+    "node_modules/@vue/language-core": {
+      "version": "2.2.12",
+      "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz",
+      "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "2.4.15",
+        "@vue/compiler-dom": "^3.5.0",
+        "@vue/compiler-vue2": "^2.7.16",
+        "@vue/shared": "^3.5.0",
+        "alien-signals": "^1.0.3",
+        "minimatch": "^9.0.3",
+        "muggle-string": "^0.4.1",
+        "path-browserify": "^1.0.1"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz",
+      "integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.32"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz",
+      "integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.32",
+        "@vue/shared": "3.5.32"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz",
+      "integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.32",
+        "@vue/runtime-core": "3.5.32",
+        "@vue/shared": "3.5.32",
+        "csstype": "^3.2.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz",
+      "integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.32",
+        "@vue/shared": "3.5.32"
+      },
+      "peerDependencies": {
+        "vue": "3.5.32"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz",
+      "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
+      "license": "MIT"
+    },
+    "node_modules/@vueuse/core": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
+      "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.20",
+        "@vueuse/metadata": "10.11.1",
+        "@vueuse/shared": "10.11.1",
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
+      "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/motion": {
+      "version": "2.2.6",
+      "resolved": "https://registry.npmjs.org/@vueuse/motion/-/motion-2.2.6.tgz",
+      "integrity": "sha512-gKFktPtrdypSv44SaW1oBJKLBiP6kE5NcoQ6RsAU3InemESdiAutgQncfPe/rhLSLCtL4jTAhMmFfxoR6gm5LQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@vueuse/core": "^10.10.0",
+        "@vueuse/shared": "^10.10.0",
+        "csstype": "^3.1.3",
+        "framesync": "^6.1.2",
+        "popmotion": "^11.0.5",
+        "style-value-types": "^5.1.2"
+      },
+      "optionalDependencies": {
+        "@nuxt/kit": "^3.13.0"
+      },
+      "peerDependencies": {
+        "vue": ">=3.0.0"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
+      "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+      "license": "MIT",
+      "dependencies": {
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+      "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/alien-signals": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
+      "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "license": "MIT"
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/arg": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+      "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+      "license": "MIT"
+    },
+    "node_modules/autoprefixer": {
+      "version": "10.4.27",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
+      "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "browserslist": "^4.28.1",
+        "caniuse-lite": "^1.0.30001774",
+        "fraction.js": "^5.3.4",
+        "picocolors": "^1.1.1",
+        "postcss-value-parser": "^4.2.0"
+      },
+      "bin": {
+        "autoprefixer": "bin/autoprefixer"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      },
+      "peerDependencies": {
+        "postcss": "^8.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.10.13",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
+      "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
+      "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+      "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==",
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.2",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+      "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "baseline-browser-mapping": "^2.10.12",
+        "caniuse-lite": "^1.0.30001782",
+        "electron-to-chromium": "^1.5.328",
+        "node-releases": "^2.0.36",
+        "update-browserslist-db": "^1.2.3"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/c12": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
+      "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "chokidar": "^5.0.0",
+        "confbox": "^0.2.4",
+        "defu": "^6.1.6",
+        "dotenv": "^17.3.1",
+        "exsolve": "^1.0.8",
+        "giget": "^3.2.0",
+        "jiti": "^2.6.1",
+        "ohash": "^2.0.11",
+        "pathe": "^2.0.3",
+        "perfect-debounce": "^2.1.0",
+        "pkg-types": "^2.3.0",
+        "rc9": "^3.0.1"
+      },
+      "peerDependencies": {
+        "magicast": "*"
+      },
+      "peerDependenciesMeta": {
+        "magicast": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/c12/node_modules/chokidar": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+      "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "readdirp": "^5.0.0"
+      },
+      "engines": {
+        "node": ">= 20.19.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/c12/node_modules/jiti": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+      "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/c12/node_modules/readdirp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+      "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 20.19.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/camelcase-css": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+      "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001784",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz",
+      "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/citty": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
+      "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "consola": "^3.2.3"
+      }
+    },
+    "node_modules/class-variance-authority": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+      "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "clsx": "^2.1.1"
+      },
+      "funding": {
+        "url": "https://polar.sh/cva"
+      }
+    },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/commander": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/confbox": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+      "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/consola": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+      "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": "^14.18.0 || >=16.10.0"
+      }
+    },
+    "node_modules/cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+      "license": "MIT",
+      "bin": {
+        "cssesc": "bin/cssesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "license": "MIT"
+    },
+    "node_modules/date-fns": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+      "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/kossnocorp"
+      }
+    },
+    "node_modules/de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/defu": {
+      "version": "6.1.6",
+      "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz",
+      "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/destr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+      "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/didyoumean": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+      "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/dlv": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+      "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+      "license": "MIT"
+    },
+    "node_modules/dotenv": {
+      "version": "17.4.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.0.tgz",
+      "integrity": "sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==",
+      "license": "BSD-2-Clause",
+      "optional": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.331",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
+      "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/entities": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/errx": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/errx/-/errx-0.1.0.tgz",
+      "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+      "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.0",
+        "@esbuild/android-arm": "0.25.0",
+        "@esbuild/android-arm64": "0.25.0",
+        "@esbuild/android-x64": "0.25.0",
+        "@esbuild/darwin-arm64": "0.25.0",
+        "@esbuild/darwin-x64": "0.25.0",
+        "@esbuild/freebsd-arm64": "0.25.0",
+        "@esbuild/freebsd-x64": "0.25.0",
+        "@esbuild/linux-arm": "0.25.0",
+        "@esbuild/linux-arm64": "0.25.0",
+        "@esbuild/linux-ia32": "0.25.0",
+        "@esbuild/linux-loong64": "0.25.0",
+        "@esbuild/linux-mips64el": "0.25.0",
+        "@esbuild/linux-ppc64": "0.25.0",
+        "@esbuild/linux-riscv64": "0.25.0",
+        "@esbuild/linux-s390x": "0.25.0",
+        "@esbuild/linux-x64": "0.25.0",
+        "@esbuild/netbsd-arm64": "0.25.0",
+        "@esbuild/netbsd-x64": "0.25.0",
+        "@esbuild/openbsd-arm64": "0.25.0",
+        "@esbuild/openbsd-x64": "0.25.0",
+        "@esbuild/sunos-x64": "0.25.0",
+        "@esbuild/win32-arm64": "0.25.0",
+        "@esbuild/win32-ia32": "0.25.0",
+        "@esbuild/win32-x64": "0.25.0"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
+    "node_modules/exsolve": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+      "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+      "license": "MIT",
+      "optional": true
+    },
+    "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==",
+      "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==",
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fastq": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+      "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+      "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==",
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/fraction.js": {
+      "version": "5.3.4",
+      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+      "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/rawify"
+      }
+    },
+    "node_modules/framesync": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz",
+      "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "2.4.0"
+      }
+    },
+    "node_modules/framesync/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "license": "0BSD"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/giget": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
+      "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "giget": "dist/cli.mjs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/hey-listen": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
+      "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
+      "license": "MIT"
+    },
+    "node_modules/i18next-browser-languagedetector": {
+      "version": "8.2.1",
+      "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
+      "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.23.2"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+      "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 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==",
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/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==",
+      "license": "MIT",
+      "peer": true,
+      "bin": {
+        "jiti": "bin/jiti.js"
+      }
+    },
+    "node_modules/klona": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
+      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/knitwork": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.3.0.tgz",
+      "integrity": "sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/lilconfig": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antonk52"
+      }
+    },
+    "node_modules/lines-and-columns": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+      "license": "MIT"
+    },
+    "node_modules/lucide-vue-next": {
+      "version": "0.354.0",
+      "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.354.0.tgz",
+      "integrity": "sha512-FVs/VXBTVSqfLywln5U0MQsB/iBdov5BBa2R9hhOaKzP9XtvuUV1jTctevEL7cgLxHpK9J4RPWSjPbPJzu1EPA==",
+      "license": "ISC",
+      "peerDependencies": {
+        "vue": ">=3.0.1"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.21",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.5"
+      }
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "license": "MIT",
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.9",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+      "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/mlly": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+      "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "acorn": "^8.16.0",
+        "pathe": "^2.0.3",
+        "pkg-types": "^1.3.1",
+        "ufo": "^1.6.3"
+      }
+    },
+    "node_modules/mlly/node_modules/confbox": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+      "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/mlly/node_modules/pkg-types": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+      "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "confbox": "^0.1.8",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.1"
+      }
+    },
+    "node_modules/muggle-string": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+      "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.37",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+      "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/ohash": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+      "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "license": "MIT"
+    },
+    "node_modules/pathe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/perfect-debounce": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+      "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+      "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/pinia": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+      "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.3",
+        "vue-demi": "^0.14.10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.4.4",
+        "vue": "^2.7.0 || ^3.5.11"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "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==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/pkg-types": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
+      "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "confbox": "^0.2.2",
+        "exsolve": "^1.0.7",
+        "pathe": "^2.0.3"
+      }
+    },
+    "node_modules/popmotion": {
+      "version": "11.0.5",
+      "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz",
+      "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==",
+      "license": "MIT",
+      "dependencies": {
+        "framesync": "6.1.2",
+        "hey-listen": "^1.0.8",
+        "style-value-types": "5.1.2",
+        "tslib": "2.4.0"
+      }
+    },
+    "node_modules/popmotion/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "license": "0BSD"
+    },
+    "node_modules/postcss": {
+      "version": "8.5.8",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+      "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+      "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",
+      "peer": true,
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-import": {
+      "version": "15.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+      "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+      "license": "MIT",
+      "dependencies": {
+        "postcss-value-parser": "^4.0.0",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.0.0"
+      }
+    },
+    "node_modules/postcss-js": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+      "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "camelcase-css": "^2.0.1"
+      },
+      "engines": {
+        "node": "^12 || ^14 || >= 16"
+      },
+      "peerDependencies": {
+        "postcss": "^8.4.21"
+      }
+    },
+    "node_modules/postcss-load-config": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+      "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "lilconfig": "^3.1.1"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "peerDependencies": {
+        "jiti": ">=1.21.0",
+        "postcss": ">=8.0.9",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        },
+        "postcss": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/postcss-nested": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+      "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "postcss-selector-parser": "^6.1.1"
+      },
+      "engines": {
+        "node": ">=12.0"
+      },
+      "peerDependencies": {
+        "postcss": "^8.2.14"
+      }
+    },
+    "node_modules/postcss-selector-parser": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+      "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+      "license": "MIT",
+      "dependencies": {
+        "cssesc": "^3.0.0",
+        "util-deprecate": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postcss-value-parser": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+      "license": "MIT"
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/rc9": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
+      "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "defu": "^6.1.6",
+        "destr": "^2.0.5"
+      }
+    },
+    "node_modules/read-cache": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+      "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+      "license": "MIT",
+      "dependencies": {
+        "pify": "^2.3.0"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/remove-accents": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
+      "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
+      "license": "MIT"
+    },
+    "node_modules/resolve": {
+      "version": "1.22.11",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+      "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.16.1",
+        "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==",
+      "license": "MIT",
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.60.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
+      "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.60.1",
+        "@rollup/rollup-android-arm64": "4.60.1",
+        "@rollup/rollup-darwin-arm64": "4.60.1",
+        "@rollup/rollup-darwin-x64": "4.60.1",
+        "@rollup/rollup-freebsd-arm64": "4.60.1",
+        "@rollup/rollup-freebsd-x64": "4.60.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.60.1",
+        "@rollup/rollup-linux-arm64-musl": "4.60.1",
+        "@rollup/rollup-linux-loong64-gnu": "4.60.1",
+        "@rollup/rollup-linux-loong64-musl": "4.60.1",
+        "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
+        "@rollup/rollup-linux-ppc64-musl": "4.60.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
+        "@rollup/rollup-linux-riscv64-musl": "4.60.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.60.1",
+        "@rollup/rollup-linux-x64-gnu": "4.60.1",
+        "@rollup/rollup-linux-x64-musl": "4.60.1",
+        "@rollup/rollup-openbsd-x64": "4.60.1",
+        "@rollup/rollup-openharmony-arm64": "4.60.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.60.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.60.1",
+        "@rollup/rollup-win32-x64-gnu": "4.60.1",
+        "@rollup/rollup-win32-x64-msvc": "4.60.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/scule": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
+      "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/style-value-types": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz",
+      "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "hey-listen": "^1.0.8",
+        "tslib": "2.4.0"
+      }
+    },
+    "node_modules/style-value-types/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "license": "0BSD"
+    },
+    "node_modules/sucrase": {
+      "version": "3.35.1",
+      "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+      "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.2",
+        "commander": "^4.0.0",
+        "lines-and-columns": "^1.1.6",
+        "mz": "^2.7.0",
+        "pirates": "^4.0.1",
+        "tinyglobby": "^0.2.11",
+        "ts-interface-checker": "^0.1.9"
+      },
+      "bin": {
+        "sucrase": "bin/sucrase",
+        "sucrase-node": "bin/sucrase-node"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/tailwind-merge": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz",
+      "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/dcastil"
+      }
+    },
+    "node_modules/tailwindcss": {
+      "version": "3.4.19",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+      "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@alloc/quick-lru": "^5.2.0",
+        "arg": "^5.0.2",
+        "chokidar": "^3.6.0",
+        "didyoumean": "^1.2.2",
+        "dlv": "^1.1.3",
+        "fast-glob": "^3.3.2",
+        "glob-parent": "^6.0.2",
+        "is-glob": "^4.0.3",
+        "jiti": "^1.21.7",
+        "lilconfig": "^3.1.3",
+        "micromatch": "^4.0.8",
+        "normalize-path": "^3.0.0",
+        "object-hash": "^3.0.0",
+        "picocolors": "^1.1.1",
+        "postcss": "^8.4.47",
+        "postcss-import": "^15.1.0",
+        "postcss-js": "^4.0.1",
+        "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+        "postcss-nested": "^6.2.0",
+        "postcss-selector-parser": "^6.1.2",
+        "resolve": "^1.22.8",
+        "sucrase": "^3.35.0"
+      },
+      "bin": {
+        "tailwind": "lib/cli.js",
+        "tailwindcss": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/tailwindcss-animate": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+      "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "tailwindcss": ">=3.0.0 || insiders"
+      }
+    },
+    "node_modules/thenify": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "node_modules/thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "license": "MIT",
+      "dependencies": {
+        "thenify": ">= 3.1.0 < 4"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tinyglobby/node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tinyglobby/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "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==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "peer": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/ufo": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+      "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/unctx": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz",
+      "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "acorn": "^8.15.0",
+        "estree-walker": "^3.0.3",
+        "magic-string": "^0.30.21",
+        "unplugin": "^2.3.11"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unplugin": {
+      "version": "2.3.11",
+      "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
+      "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@jridgewell/remapping": "^2.3.5",
+        "acorn": "^8.15.0",
+        "picomatch": "^4.0.3",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unplugin/node_modules/picomatch": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+      "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/untyped": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz",
+      "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "citty": "^0.1.6",
+        "defu": "^6.1.4",
+        "jiti": "^2.4.2",
+        "knitwork": "^1.2.0",
+        "scule": "^1.3.0"
+      },
+      "bin": {
+        "untyped": "dist/cli.mjs"
+      }
+    },
+    "node_modules/untyped/node_modules/jiti": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+      "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/vite": {
+      "version": "5.4.21",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vscode-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+      "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/vue": {
+      "version": "3.5.32",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz",
+      "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.32",
+        "@vue/compiler-sfc": "3.5.32",
+        "@vue/runtime-dom": "3.5.32",
+        "@vue/server-renderer": "3.5.32",
+        "@vue/shared": "3.5.32"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-i18n": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+      "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+      "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/core-base": "9.14.5",
+        "@intlify/shared": "9.14.5",
+        "@vue/devtools-api": "^6.5.0"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.6.4",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+      "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.5.0"
+      }
+    },
+    "node_modules/vue-sonner": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-1.3.2.tgz",
+      "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==",
+      "license": "MIT"
+    },
+    "node_modules/vue-tsc": {
+      "version": "2.2.12",
+      "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz",
+      "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/typescript": "2.4.15",
+        "@vue/language-core": "2.2.12"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": ">=5.0.0"
+      }
+    },
+    "node_modules/webpack-virtual-modules": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+      "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/zod": {
+      "version": "3.25.76",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+      "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    }
+  }
+}

+ 45 - 0
package.json

@@ -0,0 +1,45 @@
+{
+  "name": "radionica3d",
+  "private": true,
+  "version": "0.1.0",
+  "type": "module",
+  "scripts": {
+    "dev": "npm run i18n:generate && vite",
+    "build": "npm run i18n:generate && vue-tsc && vite build",
+    "i18n:generate": "python scripts/manage_locales.py split",
+    "i18n:merge": "python scripts/manage_locales.py merge",
+    "lint": "eslint . --ext ts,vue --report-unused-disable-directives --max-warnings 0",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@tanstack/vue-query": "^5.25.0",
+    "@vueuse/core": "^10.9.0",
+    "@vueuse/motion": "^2.1.0",
+    "class-variance-authority": "^0.7.0",
+    "clsx": "^2.1.0",
+    "date-fns": "^3.3.1",
+    "i18next-browser-languagedetector": "^8.2.1",
+    "lucide-vue-next": "^0.354.0",
+    "pinia": "^2.1.7",
+    "tailwind-merge": "^2.2.1",
+    "tailwindcss-animate": "^1.0.7",
+    "vue": "^3.4.21",
+    "vue-i18n": "^9.13.1",
+    "vue-router": "^4.3.0",
+    "vue-sonner": "^1.1.4",
+    "zod": "^3.22.4"
+  },
+  "devDependencies": {
+    "@types/node": "^20.11.25",
+    "@vitejs/plugin-vue": "^5.0.4",
+    "autoprefixer": "^10.4.18",
+    "postcss": "^8.4.35",
+    "tailwindcss": "^3.4.1",
+    "typescript": "^5.3.3",
+    "vite": "^5.1.5",
+    "vue-tsc": "^2.0.7"
+  },
+  "overrides": {
+    "esbuild": "0.25.0"
+  }
+}

+ 6 - 0
postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+};

+ 32 - 0
radionica-backend.service

@@ -0,0 +1,32 @@
+[Unit]
+Description=Radionica3D FastAPI Backend Service
+After=network.target
+# Раскомментируйте следующую строку, если база данных MySQL тоже крутится на этом сервере
+# After=network.target mysql.service redis.service
+
+[Service]
+# Укажите вашего Linux-пользователя (например, ubuntu, root или www-data)
+User=ubuntu
+Group=www-data
+
+# Путь до директории бэкенда
+WorkingDirectory=/var/www/radionica3d/backend
+
+# Путь к Python внутри виртуального окружения (.venv/bin/uvicorn)
+# Используем флаг --workers для запуска нескольких параллельных процессов в проде
+ExecStart=/var/www/radionica3d/backend/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4
+
+# Авторестарт при падении
+Restart=always
+RestartSec=5
+
+# Задайте переменные окружения прямо здесь (или используйте EnvironmentFile)
+Environment="NODE_ENV=production"
+Environment="DB_HOST=127.0.0.1"
+Environment="REDIS_HOST=127.0.0.1"
+# Environment="DB_USER=radionica_user"
+# Environment="DB_PASS=your_strong_password"
+# Environment="DB_NAME=radionica3d"
+
+[Install]
+WantedBy=multi-user.target

+ 108 - 0
scripts/manage_locales.py

@@ -0,0 +1,108 @@
+import json
+import os
+import sys
+from pathlib import Path
+
+LOCALES_DIR = Path("src/locales")
+MASTER_FILE = LOCALES_DIR / "translations.json"
+LANGUAGES = ["en", "me", "ru"]
+
+def get_nested_keys(data, prefix=""):
+    keys = {}
+    for k, v in data.items():
+        new_prefix = f"{prefix}.{k}" if prefix else k
+        if isinstance(v, dict):
+            # Check if this is a leaf node with language keys
+            if any(lang in v for lang in LANGUAGES):
+                keys[new_prefix] = v
+            else:
+                keys.update(get_nested_keys(v, new_prefix))
+        else:
+            keys[new_prefix] = v
+    return keys
+
+def set_nested_key(data, key_path, value):
+    parts = key_path.split('.')
+    for part in parts[:-1]:
+        data = data.setdefault(part, {})
+    data[parts[-1]] = value
+
+def merge():
+    """Merge individual JSON files into translations.json"""
+    master_data = {}
+    all_keys = set()
+    locale_data = {}
+
+    for lang in LANGUAGES:
+        path = LOCALES_DIR / f"{lang}.json"
+        if path.exists():
+            with open(path, "r", encoding="utf-8") as f:
+                data = json.load(f)
+                flat = {}
+                def flatten(d, prefix=""):
+                    for k, v in d.items():
+                        new_prefix = f"{prefix}.{k}" if prefix else k
+                        if isinstance(v, dict):
+                            flatten(v, new_prefix)
+                        else:
+                            flat[new_prefix] = v
+                flatten(data)
+                locale_data[lang] = flat
+                all_keys.update(flat.keys())
+
+    for key in sorted(all_keys):
+        translations = {}
+        for lang in LANGUAGES:
+            translations[lang] = locale_data.get(lang, {}).get(key, "")
+        
+        set_nested_key(master_data, key, translations)
+
+    with open(MASTER_FILE, "w", encoding="utf-8") as f:
+        json.dump(master_data, f, ensure_ascii=False, indent=2)
+    
+    print(f"Merged all locales into {MASTER_FILE}")
+
+def split():
+    """Split translations.json into individual JSON files"""
+    if not MASTER_FILE.exists():
+        print(f"Error: {MASTER_FILE} not found")
+        return
+
+    with open(MASTER_FILE, "r", encoding="utf-8") as f:
+        master_data = json.load(f)
+
+    for lang in LANGUAGES:
+        lang_data = {}
+        
+        def unflatten(d, target, current_lang):
+            for k, v in d.items():
+                if isinstance(v, dict):
+                    # Check if this is a leaf (contains language keys)
+                    if any(l in v for l in LANGUAGES):
+                        target[k] = v.get(current_lang, "")
+                    else:
+                        target[k] = {}
+                        unflatten(v, target[k], current_lang)
+                else:
+                    # Should not ideally happen with the current structure
+                    target[k] = v
+
+        unflatten(master_data, lang_data, lang)
+        
+        output_path = LOCALES_DIR / f"{lang}.json"
+        with open(output_path, "w", encoding="utf-8") as f:
+            json.dump(lang_data, f, ensure_ascii=False, indent=2)
+        print(f"Generated {output_path}")
+
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        print("Usage: python manage_locales.py [merge|split]")
+        sys.exit(1)
+    
+    cmd = sys.argv[1].lower()
+    if cmd == "merge":
+        merge()
+    elif cmd == "split":
+        split()
+    else:
+        print(f"Unknown command: {cmd}")

BIN
server_debug.log


+ 42 - 0
src/App.css

@@ -0,0 +1,42 @@
+#root {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}

+ 19 - 0
src/App.vue

@@ -0,0 +1,19 @@
+<template>
+  <RouterView />
+  <Toaster />
+  <CompleteProfileModal
+    v-if="authStore.user"
+    :is-open="authStore.showCompleteProfile"
+    :user="authStore.user"
+    @complete="authStore.onProfileComplete"
+  />
+</template>
+
+<script setup lang="ts">
+import { Toaster } from "vue-sonner";
+import { useAuthStore } from "@/stores/auth";
+import CompleteProfileModal from "@/components/CompleteProfileModal.vue";
+
+const authStore = useAuthStore();
+authStore.init();
+</script>

BIN
src/assets/hero-3d-print.jpg


+ 47 - 0
src/components/CTASection.vue

@@ -0,0 +1,47 @@
+<template>
+  <section class="py-24 relative overflow-hidden">
+    <div class="absolute inset-0 bg-gradient-to-b from-background via-secondary/20 to-background" />
+    <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-gradient-glow rounded-full blur-3xl opacity-50" />
+
+    <div class="container mx-auto px-4 relative z-10">
+      <div class="max-w-3xl mx-auto text-center animate-fade-in">
+        <div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary mb-6">
+          <Sparkles class="w-4 h-4" />
+          <span class="text-xs font-display font-medium tracking-wider uppercase">{{ t("hero.badge") }}</span>
+        </div>
+
+        <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mb-6">
+          {{ t("upload.title") }} <span class="text-gradient">{{ t("upload.titleGradient") }}</span>
+        </h2>
+
+        <p class="text-muted-foreground text-lg mb-10 max-w-xl mx-auto leading-relaxed">{{ t("upload.description") }}</p>
+
+        <div class="flex flex-col sm:flex-row gap-4 justify-center">
+          <Button variant="hero" size="xl" class="group" as="a" href="#upload">
+            {{ t("hero.uploadButton") }}
+            <ArrowRight class="w-5 h-5 group-hover:translate-x-1 transition-transform" />
+          </Button>
+          <Button variant="heroOutline" size="xl" as="a" href="#philosophy">
+            {{ t("nav.philosophy") }}
+          </Button>
+        </div>
+
+        <div class="flex items-center justify-center gap-6 mt-12 text-sm text-muted-foreground">
+          <span class="flex items-center gap-1.5">
+            <span class="w-1.5 h-1.5 bg-primary rounded-full" /> {{ t("pricing.trustSteps.step1") }}
+          </span>
+          <span class="flex items-center gap-1.5">
+            <span class="w-1.5 h-1.5 bg-primary rounded-full" /> {{ t("hero.stats.shipping") }}
+          </span>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from "vue-i18n";
+import { ArrowRight, Sparkles } from "lucide-vue-next";
+import Button from "./ui/button.vue";
+const { t } = useI18n();
+</script>

+ 127 - 0
src/components/CompleteProfileModal.vue

@@ -0,0 +1,127 @@
+<template>
+  <Teleport to="body">
+    <Transition
+      enter-active-class="transition duration-200"
+      enter-from-class="opacity-0"
+      enter-to-class="opacity-100"
+      leave-active-class="transition duration-150"
+      leave-from-class="opacity-100"
+      leave-to-class="opacity-0"
+    >
+      <div v-if="isOpen" class="fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6">
+        <!-- Backdrop -->
+        <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" />
+
+        <!-- Modal -->
+        <div
+          v-motion
+          :initial="{ opacity: 0, scale: 0.9, y: 20 }"
+          :enter="{ opacity: 1, scale: 1, y: 0 }"
+          class="relative w-full max-w-lg bg-card/90 border border-border/50 rounded-3xl shadow-2xl overflow-hidden backdrop-blur-xl"
+        >
+          <div class="absolute top-0 inset-x-0 h-1 bg-gradient-to-r from-primary/50 via-primary to-primary/50" />
+
+          <div class="p-8">
+            <div class="text-center mb-8">
+              <div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 text-primary mb-4">
+                <MapPin class="w-8 h-8" />
+              </div>
+              <h2 class="text-2xl font-bold font-display mb-2">
+                {{ t("profile.complete_title") || "Complete Your Profile" }}
+              </h2>
+              <p class="text-muted-foreground text-sm">
+                {{ t("profile.complete_subtitle") || "We need a few more details to process your 3D printing orders." }}
+              </p>
+            </div>
+
+            <form @submit.prevent="handleSubmit" class="space-y-6">
+              <div class="space-y-4">
+                <!-- Phone -->
+                <div class="space-y-2">
+                  <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">
+                    {{ t("upload.phone") }}
+                  </label>
+                  <div class="relative group">
+                    <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
+                      <Phone class="w-4 h-4" />
+                    </div>
+                    <input
+                      v-model="formData.phone"
+                      type="tel"
+                      required
+                      placeholder="+381 60 123 4567"
+                      class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-sm"
+                    />
+                  </div>
+                </div>
+
+                <!-- Shipping Address -->
+                <div class="space-y-2">
+                  <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">
+                    {{ t("upload.shippingAddress") }}
+                  </label>
+                  <div class="relative group">
+                    <div class="absolute top-3 left-4 text-muted-foreground group-focus-within:text-primary transition-colors">
+                      <MapPin class="w-4 h-4" />
+                    </div>
+                    <textarea
+                      v-model="formData.shipping_address"
+                      required
+                      rows="3"
+                      placeholder="Street name, Number, City, Postal Code"
+                      class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-sm resize-none"
+                    />
+                  </div>
+                </div>
+              </div>
+
+              <Button type="submit" variant="hero" class="w-full" :disabled="isLoading">
+                <Loader2 v-if="isLoading" class="w-5 h-5 animate-spin" />
+                <template v-else>
+                  {{ t("common.save_continue") || "Save & Continue" }}
+                  <ArrowRight class="w-4 h-4 ml-2" />
+                </template>
+              </Button>
+            </form>
+          </div>
+        </div>
+      </div>
+    </Transition>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from "vue";
+import { useI18n } from "vue-i18n";
+import { toast } from "vue-sonner";
+import { Phone, MapPin, Loader2, ArrowRight } from "lucide-vue-next";
+import Button from "./ui/button.vue";
+import { updateProfile } from "@/lib/api";
+
+const props = defineProps<{ isOpen: boolean; user: any }>();
+const emit = defineEmits<{ complete: [] }>();
+
+const { t } = useI18n();
+const isLoading = ref(false);
+const formData = reactive({
+  phone: props.user?.phone ?? "",
+  shipping_address: props.user?.shipping_address ?? "",
+});
+
+async function handleSubmit() {
+  if (!formData.phone || !formData.shipping_address) {
+    toast.error(t("errors.missing_fields") || "Please fill all fields");
+    return;
+  }
+  isLoading.value = true;
+  try {
+    await updateProfile(formData);
+    toast.success(t("profile.updated_success") || "Profile completed successfully!");
+    emit("complete");
+  } catch (err: any) {
+    toast.error(err.message);
+  } finally {
+    isLoading.value = false;
+  }
+}
+</script>

+ 91 - 0
src/components/Footer.vue

@@ -0,0 +1,91 @@
+<template>
+  <footer class="bg-card border-t border-border/50 pt-16 pb-8">
+    <div class="container mx-auto px-4">
+      <div class="grid sm:grid-cols-2 lg:grid-cols-5 gap-12 mb-12">
+        <!-- Brand -->
+        <div class="lg:col-span-2">
+          <Logo />
+          <p class="text-muted-foreground mt-4 mb-6 max-w-sm">{{ t("footer.tagline") }}</p>
+          <div class="space-y-3">
+            <a href="mailto:hello@forge3d.com" class="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors">
+              <Mail class="w-4 h-4 text-primary" />hello@forge3d.com
+            </a>
+            <a href="tel:+1234567890" class="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors">
+              <Phone class="w-4 h-4 text-primary" />+1 (234) 567-890
+            </a>
+            <div class="flex items-center gap-3 text-muted-foreground">
+              <MapPin class="w-4 h-4 text-primary" />San Francisco, CA
+            </div>
+          </div>
+        </div>
+
+        <!-- Services -->
+        <div>
+          <h4 class="font-display font-semibold text-foreground mb-4">{{ t("footer.services") }}</h4>
+          <ul class="space-y-3">
+            <li v-for="link in footerLinks.services" :key="link.label">
+              <a :href="link.href" class="text-muted-foreground hover:text-foreground transition-colors">{{ link.label }}</a>
+            </li>
+          </ul>
+        </div>
+
+        <!-- Company -->
+        <div>
+          <h4 class="font-display font-semibold text-foreground mb-4">{{ t("footer.company") }}</h4>
+          <ul class="space-y-3">
+            <li v-for="link in footerLinks.company" :key="link.label">
+              <a :href="link.href" class="text-muted-foreground hover:text-foreground transition-colors">{{ link.label }}</a>
+            </li>
+          </ul>
+        </div>
+
+        <!-- Support -->
+        <div>
+          <h4 class="font-display font-semibold text-foreground mb-4">{{ t("footer.support") }}</h4>
+          <ul class="space-y-3">
+            <li v-for="link in footerLinks.support" :key="link.label">
+              <a :href="link.href" class="text-muted-foreground hover:text-foreground transition-colors">{{ link.label }}</a>
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      <!-- Bottom -->
+      <div class="pt-8 border-t border-border/50 flex flex-col sm:flex-row justify-between items-center gap-4">
+        <p class="text-sm text-muted-foreground">© 2024 Radionica 3D. {{ t("footer.allRightsReserved") }}</p>
+        <div class="flex gap-6">
+          <a href="#" class="text-sm text-muted-foreground hover:text-foreground transition-colors">{{ t("footer.privacy") }}</a>
+          <a href="#" class="text-sm text-muted-foreground hover:text-foreground transition-colors">{{ t("footer.terms") }}</a>
+        </div>
+      </div>
+    </div>
+  </footer>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+import { useI18n } from "vue-i18n";
+import { Mail, Phone, MapPin } from "lucide-vue-next";
+import Logo from "./Logo.vue";
+
+const { t } = useI18n();
+
+const footerLinks = computed(() => ({
+  services: [
+    { label: t("services.fdm.title"), href: "#" },
+    { label: t("services.sla.title"), href: "#" },
+  ],
+  company: [
+    { label: t("footer.about"), href: "#" },
+    { label: t("footer.careers"), href: "#" },
+    { label: t("footer.blog"), href: "#" },
+    { label: t("footer.contact"), href: "#" },
+  ],
+  support: [
+    { label: t("footer.help"), href: "#" },
+    { label: t("footer.guidelines"), href: "#" },
+    { label: t("footer.materials"), href: "#" },
+    { label: t("footer.api"), href: "#" },
+  ],
+}));
+</script>

+ 167 - 0
src/components/Header.vue

@@ -0,0 +1,167 @@
+<template>
+  <header class="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-lg border-b border-border/50">
+    <div class="container mx-auto px-4">
+      <div class="flex items-center justify-between h-16 lg:h-20">
+        <!-- Logo -->
+        <RouterLink to="/"><Logo /></RouterLink>
+
+        <!-- Desktop Nav -->
+        <nav class="hidden lg:flex items-center gap-8">
+          <component
+            v-for="link in navLinks"
+            :key="link.href"
+            :is="link.isInternal ? RouterLink : 'a'"
+            v-bind="link.isInternal ? { to: link.href } : { href: link.href }"
+            class="text-muted-foreground hover:text-foreground transition-colors duration-300 font-medium"
+          >
+            {{ link.label }}
+          </component>
+        </nav>
+
+        <!-- Right controls -->
+        <div class="hidden lg:flex items-center gap-2">
+          <LanguageSwitcher />
+
+          <RouterLink
+            v-if="isAdmin"
+            to="/admin"
+            class="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-primary hover:bg-primary/10 rounded-lg transition-colors"
+          >
+            <LayoutPanelTop class="w-4 h-4" />
+            Admin
+          </RouterLink>
+
+          <RouterLink
+            v-if="isLoggedIn"
+            to="/orders"
+            class="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium hover:bg-secondary rounded-lg transition-colors"
+          >
+            <PackageCheck class="w-4 h-4" />
+            {{ t("nav.myOrders") || "My Orders" }}
+          </RouterLink>
+
+          <template v-if="isLoggedIn">
+            <Button variant="ghost" size="sm" class="group" @click="handleLogout">
+              <LogOut class="w-4 h-4 mr-2 group-hover:text-destructive transition-colors" />
+              {{ t("nav.logOut") || "Log Out" }}
+            </Button>
+          </template>
+          <template v-else>
+            <Button variant="ghost" size="sm" :as="RouterLink" to="/auth">
+              {{ t("nav.logIn") }}
+            </Button>
+            <Button variant="default" size="sm" :as="RouterLink" to="/auth">
+              {{ t("nav.register") }}
+            </Button>
+          </template>
+        </div>
+
+        <!-- Hamburger -->
+        <Button variant="ghost" size="icon" class="lg:hidden" @click="mobileOpen = !mobileOpen">
+          <X v-if="mobileOpen" class="w-5 h-5" />
+          <Menu v-else class="w-5 h-5" />
+        </Button>
+      </div>
+    </div>
+
+    <!-- Mobile Menu -->
+    <Transition
+      enter-active-class="transition duration-200 ease-out"
+      enter-from-class="opacity-0 -translate-y-2"
+      enter-to-class="opacity-100 translate-y-0"
+      leave-active-class="transition duration-150 ease-in"
+      leave-from-class="opacity-100 translate-y-0"
+      leave-to-class="opacity-0 -translate-y-2"
+    >
+      <div v-if="mobileOpen" class="lg:hidden py-4 border-t border-border/50 bg-background/95 backdrop-blur-lg">
+        <nav class="container mx-auto px-4 flex flex-col gap-3">
+          <component
+            v-for="link in navLinks"
+            :key="link.href"
+            :is="link.isInternal ? RouterLink : 'a'"
+            v-bind="link.isInternal ? { to: link.href } : { href: link.href }"
+            class="text-muted-foreground hover:text-foreground transition-colors py-2"
+            @click="mobileOpen = false"
+          >
+            {{ link.label }}
+          </component>
+
+          <RouterLink
+            v-if="isAdmin"
+            to="/admin"
+            class="flex items-center gap-2 text-primary py-2"
+            @click="mobileOpen = false"
+          >
+            <LayoutPanelTop class="w-4 h-4" />Admin Panel
+          </RouterLink>
+
+          <RouterLink
+            v-if="isLoggedIn"
+            to="/orders"
+            class="flex items-center gap-2 text-muted-foreground hover:text-foreground py-2"
+            @click="mobileOpen = false"
+          >
+            <PackageCheck class="w-4 h-4" />
+            {{ t("nav.myOrders") || "My Orders" }}
+          </RouterLink>
+
+          <div class="flex flex-col gap-2 pt-4 border-t border-border/50">
+            <template v-if="isLoggedIn">
+              <Button variant="ghost" class="justify-start" @click="handleLogout">
+                <LogOut class="w-4 h-4 mr-2" />{{ t("nav.logOut") || "Log Out" }}
+              </Button>
+            </template>
+            <template v-else>
+              <Button variant="ghost" class="justify-start" :as="RouterLink" to="/auth" @click="mobileOpen = false">
+                {{ t("nav.logIn") }}
+              </Button>
+              <Button variant="default" :as="RouterLink" to="/auth" @click="mobileOpen = false">
+                {{ t("nav.register") }}
+              </Button>
+            </template>
+          </div>
+
+          <div class="pt-4">
+            <LanguageSwitcher />
+          </div>
+        </nav>
+      </div>
+    </Transition>
+  </header>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from "vue";
+import { RouterLink, useRouter } from "vue-router";
+import { useI18n } from "vue-i18n";
+import { toast } from "vue-sonner";
+import { Menu, X, LayoutPanelTop, LogOut, PackageCheck } from "lucide-vue-next";
+import Logo from "./Logo.vue";
+import LanguageSwitcher from "./LanguageSwitcher.vue";
+import Button from "./ui/button.vue";
+import { useAuthStore } from "@/stores/auth";
+
+const { t } = useI18n();
+const router = useRouter();
+const authStore = useAuthStore();
+const mobileOpen = ref(false);
+
+const isLoggedIn = computed(() => !!localStorage.getItem("token"));
+const isAdmin = computed(() => authStore.user?.role === "admin");
+
+const navLinks = computed(() => [
+  { label: t("nav.services"), href: "/#services", isInternal: false },
+  { label: t("nav.materials"), href: "/#materials", isInternal: false },
+  { label: t("nav.howItWorks"), href: "/#process", isInternal: false },
+  { label: t("nav.portfolio") || "Portfolio", href: "/portfolio", isInternal: true },
+  { label: t("nav.philosophy"), href: "/#philosophy", isInternal: false },
+]);
+
+function handleLogout() {
+  localStorage.removeItem("token");
+  authStore.setUser(null);
+  toast.success("Successfully logged out");
+  router.push("/");
+  mobileOpen.value = false;
+}
+</script>

+ 71 - 0
src/components/HeroSection.vue

@@ -0,0 +1,71 @@
+<template>
+  <section class="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-hero">
+    <div class="absolute inset-0 grid-pattern opacity-50" />
+    <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-gradient-glow rounded-full blur-3xl" />
+
+    <div class="container mx-auto px-4 pt-20 lg:pt-0">
+      <div class="grid lg:grid-cols-2 gap-12 items-center">
+        <!-- Text -->
+        <div class="text-center lg:text-left animate-slide-up">
+          <div class="inline-flex items-center gap-2 px-4 py-2 bg-secondary/50 rounded-full border border-border/50 mb-6 font-display font-medium tracking-wider uppercase">
+            <span class="w-2 h-2 bg-primary rounded-full animate-pulse-glow" />
+            <span class="text-sm text-muted-foreground">{{ t("hero.badge") }}</span>
+          </div>
+
+          <h1 class="font-display text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-bold leading-tight mb-6">
+            {{ t("hero.title") }}
+            <span class="block text-gradient">{{ t("hero.titleGradient") }}</span>
+          </h1>
+
+          <p class="text-lg text-muted-foreground max-w-xl mx-auto lg:mx-0 mb-8 leading-relaxed">
+            {{ t("hero.description") }}
+          </p>
+
+          <div class="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
+            <Button variant="hero" size="xl" class="group" as="a" href="#upload">
+              <Upload class="w-5 h-5" />
+              {{ t("hero.uploadButton") }}
+              <ArrowRight class="w-5 h-5 group-hover:translate-x-1 transition-transform" />
+            </Button>
+            <Button variant="heroOutline" size="xl" as="a" href="#process">
+              {{ t("hero.pricingButton") }}
+            </Button>
+          </div>
+
+          <!-- Stats -->
+          <div class="grid grid-cols-3 gap-6 mt-12 pt-8 border-t border-border/30">
+            <div class="space-y-1">
+              <div class="font-display text-2xl sm:text-3xl font-bold text-gradient">{{ t("hero.stats.precisionValue") }}</div>
+              <div class="text-sm text-muted-foreground">{{ t("hero.stats.precision") }}</div>
+            </div>
+            <div class="space-y-1">
+              <div class="font-display text-2xl sm:text-3xl font-bold text-gradient">{{ t("hero.stats.materialsValue") }}</div>
+              <div class="text-sm text-muted-foreground">{{ t("hero.stats.materials") }}</div>
+            </div>
+            <div class="space-y-1">
+              <div class="font-display text-2xl sm:text-3xl font-bold text-gradient">{{ t("hero.stats.shippingValue") }}</div>
+              <div class="text-sm text-muted-foreground">{{ t("hero.stats.shipping") }}</div>
+            </div>
+          </div>
+        </div>
+
+        <!-- Image -->
+        <div class="relative animate-float hidden lg:block">
+          <div class="relative z-10 p-2 bg-card/20 backdrop-blur-sm rounded-2xl border border-border/50">
+            <img alt="3D Printed Professional Prototyping" class="w-full h-auto rounded-xl shadow-card" />
+          </div>
+          <div class="absolute -top-8 -right-8 w-32 h-32 border-2 border-primary/30 rounded-2xl animate-pulse" />
+          <div class="absolute -bottom-8 -left-8 w-24 h-24 bg-primary/10 rounded-2xl blur-xl" />
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from "vue-i18n";
+import { ArrowRight, Upload } from "lucide-vue-next";
+import Button from "./ui/button.vue";
+
+const { t } = useI18n();
+</script>

+ 60 - 0
src/components/LanguageSwitcher.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="relative" ref="containerRef">
+    <Button variant="ghost" size="icon" class="gap-1.5 w-auto px-2" @click="isOpen = !isOpen">
+      <Globe class="w-4 h-4" />
+      <span class="text-sm">{{ currentLang.flag }}</span>
+      <ChevronDown class="w-3 h-3" />
+    </Button>
+
+    <Transition
+      enter-active-class="transition duration-150 ease-out"
+      enter-from-class="opacity-0 scale-95 translate-y-1"
+      enter-to-class="opacity-100 scale-100 translate-y-0"
+      leave-active-class="transition duration-100 ease-in"
+      leave-from-class="opacity-100 scale-100 translate-y-0"
+      leave-to-class="opacity-0 scale-95 translate-y-1"
+    >
+      <div
+        v-if="isOpen"
+        class="absolute right-0 top-full mt-2 w-44 bg-card border border-border rounded-xl shadow-xl z-50 overflow-hidden"
+      >
+        <button
+          v-for="lang in languages"
+          :key="lang.code"
+          class="w-full flex items-center gap-2 px-4 py-2.5 text-sm hover:bg-secondary transition-colors cursor-pointer"
+          @click="changeLang(lang.code)"
+        >
+          <span>{{ lang.flag }}</span>
+          <span>{{ lang.label }}</span>
+        </button>
+      </div>
+    </Transition>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from "vue";
+import { onClickOutside } from "@vueuse/core";
+import { Globe, ChevronDown } from "lucide-vue-next";
+import Button from "./ui/button.vue";
+import { setLanguage, currentLanguage } from "@/i18n";
+
+const languages = [
+  { code: "en", label: "English", flag: "🇬🇧" },
+  { code: "ru", label: "Русский", flag: "🇷🇺" },
+  { code: "me", label: "Crnogorski", flag: "🇲🇪" },
+];
+
+const isOpen = ref(false);
+const containerRef = ref<HTMLElement | null>(null);
+const currentLang = computed(
+  () => languages.find((l) => l.code === currentLanguage()) ?? languages[0]
+);
+
+onClickOutside(containerRef, () => (isOpen.value = false));
+
+function changeLang(code: string) {
+  setLanguage(code);
+  isOpen.value = false;
+}
+</script>

+ 17 - 0
src/components/Logo.vue

@@ -0,0 +1,17 @@
+<template>
+  <div class="flex items-center gap-2">
+    <div class="relative">
+      <div class="w-10 h-10 bg-primary rounded-lg flex items-center justify-center shadow-glow">
+        <Printer class="w-6 h-6 text-primary-foreground" />
+      </div>
+      <div class="absolute -top-1 -right-1 w-3 h-3 bg-primary rounded-full animate-pulse-glow" />
+    </div>
+    <span class="font-display font-bold text-xl tracking-wider text-foreground">
+      Radionica <span class="text-primary">3D</span>
+    </span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { Printer } from "lucide-vue-next";
+</script>

+ 371 - 0
src/components/ModelUploadSection.vue

@@ -0,0 +1,371 @@
+<template>
+  <section id="upload" class="py-24 bg-background relative">
+    <div class="absolute inset-0 bg-gradient-glow opacity-50" />
+
+    <div class="container mx-auto px-4 relative z-10">
+      <div class="text-center mb-16">
+        <span class="text-primary font-display text-sm tracking-widest uppercase">{{ t("upload.badge") }}</span>
+        <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mt-4 mb-6">
+          {{ t("upload.title") }} <span class="text-gradient">{{ t("upload.titleGradient") }}</span>
+        </h2>
+        <p class="text-muted-foreground max-w-2xl mx-auto leading-relaxed">{{ t("upload.description") }}</p>
+      </div>
+
+      <div class="max-w-3xl mx-auto space-y-8">
+        <!-- Contact -->
+        <div class="grid sm:grid-cols-2 gap-6">
+          <div class="space-y-3">
+            <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+              <User class="w-4 h-4 text-primary" />{{ t("upload.firstName") }} *
+            </label>
+            <input v-model="firstName" type="text" required :placeholder="t('upload.firstName')"
+              class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm" />
+          </div>
+          <div class="space-y-3">
+            <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+              <User class="w-4 h-4 text-primary" />{{ t("upload.lastName") }} *
+            </label>
+            <input v-model="lastName" type="text" required :placeholder="t('upload.lastName')"
+              class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm" />
+          </div>
+        </div>
+
+        <div class="grid sm:grid-cols-2 gap-6">
+          <div class="space-y-3">
+            <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+              <Phone class="w-4 h-4 text-primary" />{{ t("upload.phone") }} *
+            </label>
+            <input v-model="phone" type="tel" required placeholder="+382..."
+              class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm" />
+          </div>
+          <div class="space-y-3">
+            <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+              <Mail class="w-4 h-4 text-primary" />{{ t("upload.email") }} *
+            </label>
+            <input v-model="email" type="email" required placeholder="example@mail.com"
+              class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm" />
+          </div>
+        </div>
+
+        <!-- Material Selection -->
+        <div class="space-y-4 p-6 bg-card/30 rounded-2xl border border-border/50">
+          <h3 class="text-sm font-semibold uppercase tracking-wider text-muted-foreground flex items-center gap-2">
+            <FileBox class="w-4 h-4 text-primary" />{{ t("upload.selectMaterial") || "Select Material" }}
+          </h3>
+          <div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
+            <button
+              v-for="m in materials"
+              :key="m.id"
+              type="button"
+              @click="selectedMaterial = String(m.id)"
+              :class="[
+                'p-4 rounded-xl border text-left transition-all hover:scale-[1.02] active:scale-[0.98]',
+                selectedMaterial === String(m.id)
+                  ? 'bg-primary/20 border-primary shadow-glow ring-1 ring-primary/40'
+                  : 'bg-background/50 border-border/50 hover:border-primary/30'
+              ]"
+            >
+              <p class="font-bold text-sm mb-1">{{ m['name_' + locale] || m.name_en }}</p>
+              <p class="text-[10px] text-muted-foreground leading-tight">{{ m['desc_' + locale] || m.desc_en }}</p>
+            </button>
+          </div>
+        </div>
+
+        <!-- Quantity & Model Link -->
+        <div class="grid sm:grid-cols-5 gap-6">
+          <!-- Removed global quantity -->
+          <div class="space-y-3 sm:col-span-3">
+            <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+              <LinkIcon class="w-4 h-4 text-primary" />{{ t("upload.modelLink") }}
+            </label>
+            <input v-model="modelLink" type="url" :placeholder="t('upload.modelLinkPlaceholder')"
+              class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm" />
+          </div>
+        </div>
+
+        <!-- Notes -->
+        <div class="space-y-3">
+          <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+            <FileText class="w-4 h-4 text-primary" />{{ t("upload.notes") }}
+          </label>
+          <textarea v-model="notes" :placeholder="t('upload.notesPlaceholder')" rows="2"
+            class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm resize-none" />
+        </div>
+
+        <!-- Drop Zone -->
+        <div
+          @dragover.prevent="isDragging = true"
+          @dragleave.prevent="isDragging = false"
+          @drop.prevent="handleDrop"
+          :class="[
+            'relative border-2 border-dashed rounded-2xl p-12 text-center transition-all duration-300',
+            isDragging ? 'border-primary bg-primary/10 shadow-glow' : 'border-border hover:border-primary/50 hover:bg-card/50'
+          ]"
+        >
+          <input type="file" multiple accept=".stl,.obj,.3mf,.step" @change="handleFileSelect"
+            class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
+          <div class="flex flex-col items-center gap-4">
+            <div :class="['w-20 h-20 rounded-full flex items-center justify-center transition-all duration-300',
+              isDragging ? 'bg-primary text-primary-foreground scale-110' : 'bg-primary/10 text-primary']">
+              <Upload class="w-10 h-10" />
+            </div>
+            <div>
+              <p class="font-display text-xl font-semibold mb-2">
+                {{ isDragging ? t("upload.dropzoneActive") : t("upload.dropzone") }}
+              </p>
+              <p class="text-muted-foreground">
+                or <span class="text-primary underline cursor-pointer">{{ t("upload.browse") }}</span>
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <!-- Shipping Address -->
+        <div class="space-y-3">
+          <label class="flex items-center gap-2 text-sm font-medium text-foreground ml-1">
+            <MapPin class="w-4 h-4 text-primary" />{{ t("upload.shippingAddress") }} *
+          </label>
+          <textarea v-model="address" rows="3" required :placeholder="t('upload.addressPlaceholder')"
+            class="w-full bg-card/50 border border-border/50 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all backdrop-blur-sm resize-none" />
+        </div>
+
+        <!-- File list -->
+        <div v-if="files.length" class="space-y-3">
+          <h3 class="font-display text-lg font-semibold mb-4">{{ t("upload.uploadedFiles") }} ({{ files.length }})</h3>
+          <div v-for="file in files" :key="file.id"
+            class="flex items-center gap-4 p-4 bg-gradient-card rounded-xl border border-border/50 hover:border-primary/30 transition-colors">
+            <div class="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center">
+              <FileBox class="w-6 h-6 text-primary" />
+            </div>
+            <div class="flex-1 min-w-0">
+              <div class="flex items-center gap-2">
+                <p class="font-medium truncate">{{ file.name }}</p>
+                <div v-if="file.isUploading" class="w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
+              </div>
+              <div class="flex items-center gap-2 text-sm text-muted-foreground">
+                <span>{{ file.type }} • {{ formatFileSize(file.size) }}</span>
+                <span v-if="file.basePrice" class="font-semibold text-primary ml-2 bg-primary/10 px-2 py-0.5 rounded-md">
+                  ~{{ (file.basePrice * file.quantity).toFixed(2) }} EUR
+                </span>
+              </div>
+              <div v-if="SHOW_ADVANCED_METRICS && file.printTime" class="flex gap-3 text-xs text-muted-foreground mt-0.5 opacity-80">
+                <span v-if="file.printTime">⏱️ {{ file.printTime }}</span>
+                <span v-if="file.filamentG">⚖️ {{ file.filamentG.toFixed(1) }}g</span>
+              </div>
+            </div>
+            <!-- Per-file quantity control -->
+            <div class="flex items-center gap-1 bg-card/50 border border-border/50 rounded-lg p-1">
+              <button type="button" @click.prevent="file.quantity > 1 && file.quantity--" class="w-8 h-8 rounded-md flex items-center justify-center hover:bg-muted text-muted-foreground transition-colors">-</button>
+              <input type="number" v-model.number="file.quantity" min="1" step="1" class="w-12 text-center bg-transparent border-none text-sm font-medium focus:ring-0 [&::-webkit-inner-spin-button]:appearance-none" />
+              <button type="button" @click.prevent="file.quantity++" class="w-8 h-8 rounded-md flex items-center justify-center hover:bg-muted text-muted-foreground transition-colors">+</button>
+            </div>
+            <div class="flex items-center gap-2">
+              <div class="w-8 h-8 bg-green-500/10 rounded-full flex items-center justify-center">
+                <Check class="w-4 h-4 text-green-500" />
+              </div>
+              <button @click="removeFile(file.id)"
+                class="w-8 h-8 hover:bg-destructive/10 rounded-full flex items-center justify-center transition-colors group">
+                <X class="w-4 h-4 text-muted-foreground group-hover:text-destructive" />
+              </button>
+            </div>
+          </div>
+        </div>
+
+        <!-- Portfolio consent -->
+        <div class="flex items-start gap-4 p-4 mt-6 bg-primary/5 border border-primary/20 rounded-2xl cursor-pointer hover:bg-primary/10 transition-all active:scale-[0.99] group"
+          @click="allowPortfolio = !allowPortfolio">
+          <div :class="['mt-0.5 w-5 h-5 rounded border flex items-center justify-center transition-all',
+            allowPortfolio ? 'bg-primary border-primary' : 'bg-background border-border group-hover:border-primary/50']">
+            <Check v-if="allowPortfolio" class="w-3.5 h-3.5 text-primary-foreground" />
+          </div>
+          <div class="flex-1 space-y-1">
+            <p class="text-sm font-medium leading-none">{{ t("upload.allowPortfolio") || "Allow featuring in public portfolio" }}</p>
+            <p class="text-xs text-muted-foreground leading-relaxed">{{ t("upload.allowPortfolioDesc") || "We'll show photos of your print to inspire other customers." }}</p>
+          </div>
+          <ShieldCheck :class="['w-5 h-5 transition-colors', allowPortfolio ? 'text-primary' : 'text-muted-foreground/30']" />
+        </div>
+
+        <!-- Estimate -->
+        <Transition enter-active-class="transition duration-300" enter-from-class="opacity-0 translate-y-2" enter-to-class="opacity-100 translate-y-0">
+          <div v-if="estimatedPrice !== null"
+            class="p-6 bg-gradient-to-br from-primary/20 via-primary/5 to-background border border-primary/30 rounded-2xl flex items-center justify-between">
+            <div>
+              <p class="text-[10px] font-bold uppercase tracking-widest text-primary/80 mb-1">Estimated Total</p>
+              <p class="text-xs text-muted-foreground">Final price will be confirmed by admin.</p>
+            </div>
+            <div class="text-right">
+              <span class="text-3xl font-display font-bold text-primary">{{ estimatedPrice }}</span>
+              <span class="text-xs font-bold text-primary ml-1 uppercase">EUR</span>
+            </div>
+          </div>
+        </Transition>
+
+        <Button variant="hero" class="w-full mt-8 shadow-lg hover:shadow-primary/20"
+          :disabled="!isFormValid || isSubmitting" @click="handleSubmit">
+          <div v-if="isSubmitting" class="flex items-center gap-2">
+            <Loader2 class="w-5 h-5 animate-spin" />{{ t("upload.submitting") || "Sending..." }}
+          </div>
+          <span v-else>{{ t("upload.continue") }}</span>
+        </Button>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { toast } from "vue-sonner";
+import { Upload, FileBox, X, Check, Link as LinkIcon, MapPin, User, Phone, Mail, Loader2, ShieldCheck, Hash, FileText } from "lucide-vue-next";
+import Button from "./ui/button.vue";
+import { submitOrder, getCurrentUser, getMaterials, getPriceEstimate, uploadFilesToServer } from "@/lib/api";
+
+interface UploadedFile { id: string; dbId?: number; name: string; size: number; type: string; file: File; quantity: number; basePrice?: number; isUploading?: boolean; printTime?: string; filamentG?: number; }
+
+// --- CONFIGURATION ---
+// Set to false to hide the hardcore slicing data (time and weight) from the end-user
+const SHOW_ADVANCED_METRICS = true;
+
+const { t, locale } = useI18n();
+const files = ref<UploadedFile[]>([]);
+const isDragging = ref(false);
+const isSubmitting = ref(false);
+const modelLink = ref("");
+const address = ref("");
+const firstName = ref("");
+const lastName = ref("");
+const phone = ref("");
+const email = ref("");
+const allowPortfolio = ref(false);
+const materials = ref<any[]>([]);
+const selectedMaterial = ref("1");
+const estimatedPrice = computed(() => {
+  if (files.value.length === 0) return null;
+  const total = files.value.reduce((acc, f) => acc + ((f.basePrice || 0) * f.quantity), 0);
+  return parseFloat(total.toFixed(2));
+});
+const notes = ref("");
+
+onMounted(async () => {
+  try {
+    const materialData = await getMaterials();
+    materials.value = materialData;
+    if (materialData.length > 0) selectedMaterial.value = String(materialData[0].id);
+    const token = localStorage.getItem("token");
+    if (token) {
+      const user = await getCurrentUser();
+      if (user) {
+        firstName.value = user.first_name ?? "";
+        lastName.value = user.last_name ?? "";
+        phone.value = user.phone ?? "";
+        email.value = user.email ?? "";
+        address.value = user.shipping_address ?? "";
+      }
+    }
+  } catch (e) { console.error("Failed to load initial data:", e); }
+});
+
+watch([() => files.value.length, selectedMaterial, modelLink], async () => {
+  if ((files.value.length > 0) && selectedMaterial.value) {
+    try {
+      const res = await getPriceEstimate({
+        material_id: parseInt(selectedMaterial.value),
+        file_sizes: files.value.map(f => f.size),
+        file_quantities: files.value.map(f => 1), // Only get base unit cost
+      });
+      // Assign individual prices to files for local quantity multiplication
+      if (res.file_prices && Array.isArray(res.file_prices)) {
+        files.value.forEach((f, i) => {
+          f.basePrice = res.file_prices[i];
+        });
+        // Force reactivity update
+        files.value = [...files.value];
+      }
+    } catch { /* silent */ }
+  }
+});
+
+const isFormValid = computed(() =>
+  firstName.value.trim() !== "" && lastName.value.trim() !== "" &&
+  phone.value.trim() !== "" && email.value.trim() !== "" &&
+  address.value.trim() !== "" && selectedMaterial.value !== "" &&
+  (files.value.length > 0 || modelLink.value.trim() !== "")
+);
+
+async function addFiles(rawFiles: File[]) {
+  const valid = rawFiles.filter(f => /\.(stl|obj|3mf|step)$/i.test(f.name));
+  
+  const newFiles: UploadedFile[] = valid.map(f => ({
+    id: Math.random().toString(36).substring(7),
+    name: f.name,
+    size: f.size,
+    type: f.name.split(".").pop()?.toUpperCase() ?? "UNKNOWN",
+    file: f,
+    quantity: 1,
+    basePrice: 0,
+    isUploading: true,
+  }));
+  
+  files.value.push(...newFiles);
+  
+  if (newFiles.length > 0) {
+    try {
+      const fd = new FormData();
+      newFiles.forEach(f => fd.append("files", f.file));
+      const res = await uploadFilesToServer(fd);
+      
+      if (res.uploaded) {
+        res.uploaded.forEach((u: any, idx: number) => {
+          newFiles[idx].dbId = u.id;
+          newFiles[idx].isUploading = false;
+          if (u.print_time) newFiles[idx].printTime = u.print_time;
+          if (u.filament_g) newFiles[idx].filamentG = u.filament_g;
+        });
+      }
+    } catch (err) {
+      console.error("Failed to upload files initially", err);
+      newFiles.forEach(f => f.isUploading = false);
+    }
+    // Deep reactivity trigger
+    files.value = [...files.value];
+  }
+}
+function handleDrop(e: DragEvent) { isDragging.value = false; addFiles(Array.from(e.dataTransfer?.files ?? [])); }
+function handleFileSelect(e: Event) { addFiles(Array.from((e.target as HTMLInputElement).files ?? [])); }
+function removeFile(id: string) { files.value = files.value.filter(f => f.id !== id); }
+function formatFileSize(bytes: number) {
+  if (bytes < 1024) return bytes + " B";
+  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
+  return (bytes / 1048576).toFixed(1) + " MB";
+}
+
+async function handleSubmit() {
+  if (!isFormValid.value) return;
+  isSubmitting.value = true;
+  const fd = new FormData();
+  fd.append("first_name", firstName.value);
+  fd.append("last_name", lastName.value);
+  fd.append("phone", phone.value);
+  fd.append("email", email.value);
+  fd.append("shipping_address", address.value);
+  fd.append("model_link", modelLink.value);
+  fd.append("allow_portfolio", String(allowPortfolio.value));
+  fd.append("notes", notes.value);
+  fd.append("material_id", selectedMaterial.value);
+  
+  const uploadedFiles = files.value.filter(f => f.dbId);
+  fd.append("file_ids", JSON.stringify(uploadedFiles.map(f => f.dbId)));
+  fd.append("file_quantities", JSON.stringify(uploadedFiles.map(f => f.quantity)));
+  try {
+    await submitOrder(fd);
+    toast.success(t("upload.success") || "Order submitted successfully!");
+    files.value = []; firstName.value = ""; lastName.value = ""; phone.value = "";
+    email.value = ""; address.value = ""; modelLink.value = "";
+    allowPortfolio.value = false; notes.value = "";
+  } catch (err: any) {
+    toast.error(err.message || "Failed to submit order.");
+  } finally {
+    isSubmitting.value = false;
+  }
+}
+</script>

+ 228 - 0
src/components/OrderChat.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="order-chat flex flex-col" :class="compact ? 'h-[320px]' : 'h-[420px]'">
+    <!-- Header -->
+    <div class="flex items-center justify-between px-4 py-3 border-b border-border/50 bg-card/60 backdrop-blur-sm rounded-t-2xl">
+      <div class="flex items-center gap-2">
+        <MessageCircle class="w-4 h-4 text-primary" />
+        <span class="text-sm font-bold">{{ t("chat.title") }}</span>
+        <span v-if="messages.length" class="text-[10px] bg-primary/10 text-primary px-1.5 py-0.5 rounded-full font-bold">{{ messages.length }}</span>
+        <span class="w-2 h-2 rounded-full" :class="wsConnected ? 'bg-emerald-500' : 'bg-rose-500'" :title="wsConnected ? 'Connected' : 'Disconnected'" />
+      </div>
+      <button v-if="closable" @click="$emit('close')" class="p-1 hover:bg-secondary rounded-lg transition-colors">
+        <X class="w-4 h-4 text-muted-foreground" />
+      </button>
+    </div>
+
+    <!-- Messages area -->
+    <div ref="messagesContainer" class="flex-1 overflow-y-auto px-4 py-3 space-y-3 scrollbar-thin">
+      <div v-if="isLoading" class="flex items-center justify-center h-full">
+        <Loader2 class="w-6 h-6 animate-spin text-primary" />
+      </div>
+
+      <div v-else-if="messages.length === 0" class="flex flex-col items-center justify-center h-full text-center opacity-60">
+        <MessageCircle class="w-10 h-10 text-muted-foreground mb-3 opacity-40" />
+        <p class="text-sm text-muted-foreground">{{ t("chat.empty") }}</p>
+      </div>
+
+      <template v-else>
+        <div
+          v-for="msg in messages"
+          :key="msg.id"
+          class="flex"
+          :class="msg.is_from_admin ? 'justify-start' : 'justify-end'"
+        >
+          <div
+            class="max-w-[80%] px-3.5 py-2 rounded-2xl text-sm leading-relaxed shadow-sm"
+            :class="msg.is_from_admin
+              ? 'bg-secondary/80 text-foreground rounded-bl-md'
+              : 'bg-primary text-primary-foreground rounded-br-md'"
+          >
+            <div v-if="msg.is_from_admin" class="flex items-center gap-1.5 mb-1">
+              <ShieldCheck class="w-3 h-3 opacity-70" />
+              <span class="text-[10px] font-bold uppercase tracking-wider opacity-70">{{ t("chat.admin") }}</span>
+            </div>
+            <p class="whitespace-pre-wrap break-words">{{ msg.message }}</p>
+            <p class="text-[10px] mt-1 opacity-50 text-right">{{ formatTime(msg.created_at) }}</p>
+          </div>
+        </div>
+      </template>
+    </div>
+
+    <!-- Input area -->
+    <div class="px-3 py-3 border-t border-border/50 bg-card/40 rounded-b-2xl">
+      <form @submit.prevent="handleSend" class="flex gap-2 items-end">
+        <textarea
+          v-model="newMessage"
+          :placeholder="t('chat.placeholder')"
+          rows="1"
+          class="flex-1 resize-none bg-background/80 border border-border/50 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/40 transition-all placeholder:text-muted-foreground/50 max-h-[80px] overflow-y-auto"
+          @keydown.enter.exact.prevent="handleSend"
+          @input="autoResize"
+          ref="textareaRef"
+        />
+        <button
+          type="submit"
+          :disabled="!newMessage.trim() || isSending"
+          class="p-2.5 bg-primary text-primary-foreground rounded-xl hover:bg-primary/90 transition-all disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0"
+        >
+          <Send v-if="!isSending" class="w-4 h-4" />
+          <Loader2 v-else class="w-4 h-4 animate-spin" />
+        </button>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted, nextTick, watch } from "vue";
+import { useI18n } from "vue-i18n";
+import { MessageCircle, Send, Loader2, ShieldCheck, X } from "lucide-vue-next";
+import { getOrderMessages, sendOrderMessage } from "@/lib/api";
+
+const WS_BASE_URL = "ws://localhost:8000";
+
+const props = defineProps<{
+  orderId: number;
+  compact?: boolean;
+  closable?: boolean;
+}>();
+
+defineEmits<{ (e: 'close'): void }>();
+
+const { t } = useI18n();
+const messages = ref<any[]>([]);
+const newMessage = ref("");
+const isLoading = ref(true);
+const isSending = ref(false);
+const wsConnected = ref(false);
+const messagesContainer = ref<HTMLElement | null>(null);
+const textareaRef = ref<HTMLTextAreaElement | null>(null);
+
+let ws: WebSocket | null = null;
+let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
+
+function connectWebSocket() {
+  const token = localStorage.getItem("token");
+  if (!token) return;
+
+  const url = `${WS_BASE_URL}/ws/chat/${props.orderId}?token=${encodeURIComponent(token)}`;
+  ws = new WebSocket(url);
+
+  ws.onopen = () => {
+    wsConnected.value = true;
+  };
+
+  ws.onmessage = (event) => {
+    try {
+      const msg = JSON.parse(event.data);
+      // Avoid duplicates (we already optimistically added our own message)
+      if (!messages.value.find(m => m.id === msg.id)) {
+        messages.value.push(msg);
+        scrollToBottom();
+      }
+    } catch (e) {
+      console.error("WS parse error:", e);
+    }
+  };
+
+  ws.onclose = () => {
+    wsConnected.value = false;
+    // Auto-reconnect after 3 seconds
+    reconnectTimer = setTimeout(() => connectWebSocket(), 3000);
+  };
+
+  ws.onerror = () => {
+    ws?.close();
+  };
+}
+
+function disconnectWebSocket() {
+  if (reconnectTimer) {
+    clearTimeout(reconnectTimer);
+    reconnectTimer = null;
+  }
+  if (ws) {
+    ws.onclose = null; // prevent auto-reconnect
+    ws.close();
+    ws = null;
+  }
+  wsConnected.value = false;
+}
+
+async function loadMessages() {
+  try {
+    messages.value = await getOrderMessages(props.orderId);
+  } catch (e) {
+    console.error("Failed to load messages:", e);
+  } finally {
+    isLoading.value = false;
+  }
+}
+
+async function handleSend() {
+  const text = newMessage.value.trim();
+  if (!text || isSending.value) return;
+
+  isSending.value = true;
+  try {
+    await sendOrderMessage(props.orderId, text);
+    newMessage.value = "";
+    if (textareaRef.value) {
+      textareaRef.value.style.height = "auto";
+    }
+    // The server will broadcast back via WS — but also reload as fallback
+    if (!wsConnected.value) {
+      await loadMessages();
+    }
+    scrollToBottom();
+  } catch (e) {
+    console.error("Failed to send message:", e);
+  } finally {
+    isSending.value = false;
+  }
+}
+
+function scrollToBottom() {
+  nextTick(() => {
+    if (messagesContainer.value) {
+      messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
+    }
+  });
+}
+
+function autoResize(e: Event) {
+  const el = e.target as HTMLTextAreaElement;
+  el.style.height = "auto";
+  el.style.height = Math.min(el.scrollHeight, 80) + "px";
+}
+
+function formatTime(dt: string) {
+  if (!dt) return "";
+  const d = new Date(dt);
+  return d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
+}
+
+onMounted(async () => {
+  await loadMessages();
+  scrollToBottom();
+  connectWebSocket();
+});
+
+onUnmounted(() => {
+  disconnectWebSocket();
+});
+
+watch(() => props.orderId, async () => {
+  disconnectWebSocket();
+  isLoading.value = true;
+  await loadMessages();
+  scrollToBottom();
+  connectWebSocket();
+});
+</script>
+
+<style scoped>
+.scrollbar-thin::-webkit-scrollbar { width: 4px; }
+.scrollbar-thin::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 9999px; }
+.scrollbar-thin::-webkit-scrollbar-track { background: transparent; }
+</style>

+ 49 - 0
src/components/ProcessSection.vue

@@ -0,0 +1,49 @@
+<template>
+  <section id="process" class="py-24 relative overflow-hidden bg-card/30">
+    <div class="container mx-auto px-4">
+      <div class="text-center mb-16 animate-slide-up">
+        <span class="text-primary font-display text-sm tracking-widest uppercase">{{ t("nav.howItWorks") }}</span>
+        <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mt-4 mb-6">
+          {{ t("hero.title") }} <span class="text-gradient">{{ t("hero.titleGradient") }}</span>
+        </h2>
+        <p class="text-muted-foreground max-w-2xl mx-auto">{{ t("hero.description") }}</p>
+      </div>
+
+      <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
+        <div v-for="(step, index) in steps" :key="step.step" class="relative group">
+          <!-- Connector -->
+          <div v-if="index < steps.length - 1" class="hidden lg:block absolute top-10 left-[60%] w-full h-0.5 bg-gradient-to-r from-primary/30 to-transparent z-0" />
+
+          <div class="relative z-10 text-center flex flex-col items-center">
+            <div class="relative inline-block mb-6">
+              <div class="w-20 h-20 bg-secondary rounded-2xl flex items-center justify-center border border-border/50 group-hover:border-primary/50 transition-all duration-500 group-hover:bg-primary/5 group-hover:rotate-6">
+                <component :is="step.icon" class="w-8 h-8 text-primary transition-transform group-hover:scale-110" />
+              </div>
+              <span class="absolute -top-2 -right-2 w-8 h-8 bg-primary text-primary-foreground font-display font-bold text-sm rounded-lg flex items-center justify-center shadow-glow">
+                {{ step.step }}
+              </span>
+            </div>
+            <h3 class="font-display text-lg font-semibold max-w-[200px] leading-snug">{{ step.title }}</h3>
+            <div v-if="index < steps.length - 1" class="lg:hidden mt-4 text-primary/30 rotate-90">
+              <ArrowRight class="w-6 h-6" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+import { useI18n } from "vue-i18n";
+import { Send, Sparkles, Package, CreditCard, ArrowRight } from "lucide-vue-next";
+
+const { t } = useI18n();
+const steps = computed(() => [
+  { icon: Send,       step: "01", title: t("pricing.trustSteps.step1") },
+  { icon: Sparkles,   step: "02", title: t("pricing.trustSteps.step2") },
+  { icon: Package,    step: "03", title: t("pricing.trustSteps.step3") },
+  { icon: CreditCard, step: "04", title: t("pricing.trustSteps.step4") },
+]);
+</script>

+ 82 - 0
src/components/QuotingSection.vue

@@ -0,0 +1,82 @@
+<template>
+  <section id="philosophy" class="py-24 bg-background relative overflow-hidden">
+    <div class="absolute inset-0 bg-gradient-glow opacity-30" />
+
+    <div class="container mx-auto px-4 relative z-10">
+      <div class="text-center mb-20 animate-slide-up">
+        <div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary mb-6">
+          <Shield class="w-4 h-4" />
+          <span class="text-xs font-display font-medium tracking-wider uppercase">{{ t("pricing.badge") }}</span>
+        </div>
+        <h2 class="font-display text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
+          {{ t("pricing.title") }} <span class="text-gradient">{{ t("pricing.titleGradient") }}</span>
+        </h2>
+        <p class="text-muted-foreground max-w-2xl mx-auto text-lg leading-relaxed">{{ t("pricing.description") }}</p>
+      </div>
+
+      <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
+        <div
+          v-for="(step, index) in steps"
+          :key="index"
+          class="group relative p-8 bg-gradient-card rounded-2xl border border-border/50 hover:border-primary/50 transition-all duration-500 hover:shadow-hover animate-scale-in"
+          :style="{ animationDelay: step.delay }"
+        >
+          <div class="absolute -top-4 -left-4 w-12 h-12 bg-background border border-border rounded-full flex items-center justify-center font-display font-bold text-primary shadow-sm z-20 group-hover:scale-110 transition-transform">
+            0{{ index + 1 }}
+          </div>
+          <div class="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mb-6 group-hover:bg-primary group-hover:text-primary-foreground transition-all duration-500 group-hover:rotate-6">
+            <component :is="step.icon" class="w-8 h-8 transition-transform group-hover:scale-110" />
+          </div>
+          <h3 class="font-display text-xl font-semibold mb-4 leading-snug group-hover:text-primary transition-colors">{{ step.title }}</h3>
+          <div v-if="index < steps.length - 1" class="hidden lg:block absolute top-1/2 -right-4 w-8 h-[2px] bg-gradient-to-r from-primary/30 to-transparent z-0" />
+        </div>
+      </div>
+
+      <!-- Philosophy block -->
+      <div class="mt-24 p-8 sm:p-12 rounded-3xl bg-card border border-border/50 relative overflow-hidden group animate-fade-in">
+        <div class="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-3xl -mr-32 -mt-32 transition-transform group-hover:scale-110 duration-700" />
+        <div class="grid lg:grid-cols-2 gap-12 items-center relative z-10">
+          <div class="space-y-6">
+            <h3 class="font-display text-3xl font-bold">
+              {{ t("whyTrust.title") }} <span class="text-primary font-mono tracking-tighter italic">{{ t("whyTrust.titleItalic") }}</span>?
+            </h3>
+            <div class="space-y-4 text-muted-foreground text-lg leading-relaxed">
+              <p>{{ t("whyTrust.description1") }}</p>
+              <p>{{ t("whyTrust.description2") }}</p>
+            </div>
+          </div>
+          <div class="grid grid-cols-2 gap-4">
+            <div
+              v-for="item in trustItems"
+              :key="item.label"
+              class="p-6 rounded-2xl bg-background border border-border/50 flex flex-col items-center text-center group/item hover:border-primary/30 transition-colors"
+            >
+              <div class="text-3xl font-bold text-primary mb-1 group-hover/item:scale-110 transition-transform">{{ item.value }}</div>
+              <div class="text-xs uppercase tracking-widest text-muted-foreground font-semibold">{{ item.label }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+import { useI18n } from "vue-i18n";
+import { Shield, Send, Sparkles, Package, CreditCard, CheckCircle2 } from "lucide-vue-next";
+
+const { t } = useI18n();
+const steps = computed(() => [
+  { icon: Send,       title: t("pricing.trustSteps.step1"), delay: "0ms" },
+  { icon: Sparkles,   title: t("pricing.trustSteps.step2"), delay: "100ms" },
+  { icon: Package,    title: t("pricing.trustSteps.step3"), delay: "200ms" },
+  { icon: CreditCard, title: t("pricing.trustSteps.step4"), delay: "300ms" },
+]);
+const trustItems = computed(() => [
+  { label: t("whyTrust.items.noPrepayment"), value: "100%" },
+  { label: t("whyTrust.items.shipping"),     value: "Fast" },
+  { label: t("whyTrust.items.yourPrice"),    value: "Fair" },
+  { label: t("whyTrust.items.noCommissions"),value: "Open" },
+]);
+</script>

+ 90 - 0
src/components/ServicesSection.vue

@@ -0,0 +1,90 @@
+<template>
+  <section id="services" class="py-24 bg-card relative overflow-hidden">
+    <div class="absolute inset-0 grid-pattern opacity-30" />
+
+    <div v-if="isLoading" class="py-24 flex items-center justify-center">
+      <Loader2 class="w-8 h-8 animate-spin text-primary" />
+    </div>
+
+    <div v-else class="container mx-auto px-4 relative z-10">
+      <div class="text-center mb-16">
+        <span class="text-primary font-display text-sm tracking-widest uppercase">{{ t("services.badge") }}</span>
+        <h2 class="font-display text-3xl sm:text-4xl lg:text-5xl font-bold mt-4 mb-6">
+          {{ t("services.title") }} <span class="text-gradient">{{ t("services.titleGradient") }}</span>
+        </h2>
+        <p class="text-muted-foreground max-w-2xl mx-auto">{{ t("services.description") }}</p>
+      </div>
+
+      <!-- Services Grid -->
+      <div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-24">
+        <div
+          v-for="(service, index) in services"
+          :key="service.id"
+          class="group p-6 bg-gradient-card rounded-xl border border-border/50 hover:border-primary/50 transition-all duration-300 hover:shadow-hover"
+          :style="{ animationDelay: `${index * 100}ms` }"
+        >
+          <div class="w-14 h-14 bg-primary/10 rounded-xl flex items-center justify-center mb-4 group-hover:bg-primary group-hover:text-primary-foreground transition-all duration-500">
+            <component :is="iconMap[service.tech_type] || iconMap.DEFAULT" class="w-7 h-7" />
+          </div>
+          <h3 class="font-display text-xl font-semibold mb-3">{{ t(service.name_key) }}</h3>
+          <p class="text-muted-foreground leading-relaxed">{{ t(service.description_key) }}</p>
+        </div>
+      </div>
+
+      <!-- Materials -->
+      <div id="materials" class="mt-20">
+        <div class="flex items-center gap-3 mb-10">
+          <div class="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
+            <Sparkles class="w-5 h-5 text-primary" />
+          </div>
+          <h3 class="font-display text-2xl font-bold">{{ t("pricing.materials") }}</h3>
+        </div>
+        <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
+          <div
+            v-for="(material, idx) in materials"
+            :key="material.id"
+            class="p-5 bg-background border border-border/40 rounded-2xl hover:border-primary/30 transition-colors group animate-fade-in"
+            :style="{ animationDelay: `${idx * 50}ms` }"
+          >
+            <div class="text-lg font-display font-bold text-foreground mb-1 group-hover:text-primary transition-colors uppercase">
+              {{ material[`name_${locale}`] || material.name_en }}
+            </div>
+            <p class="text-xs text-muted-foreground line-clamp-2 leading-relaxed">{{ material[`desc_${locale}`] || material.desc_en }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { Layers, Zap, Loader2, Sparkles, Box, Cpu } from "lucide-vue-next";
+import { getServices, getMaterials } from "@/lib/api";
+
+const { t, locale } = useI18n();
+
+interface Service { id: number; name_key: string; description_key: string; tech_type: string }
+interface Material { 
+  id: number; 
+  name_en: string; name_ru: string; name_me: string;
+  desc_en: string; desc_ru: string; desc_me: string;
+  price_per_cm3: number 
+}
+
+const iconMap: Record<string, any> = { FDM: Layers, SLA: Zap, DEFAULT: Box };
+const services = ref<Service[]>([]);
+const materials = ref<Material[]>([]);
+const isLoading = ref(true);
+
+onMounted(async () => {
+  try {
+    [services.value, materials.value] = await Promise.all([getServices(), getMaterials()]);
+  } catch (e) {
+    console.error("Failed to fetch services/materials:", e);
+  } finally {
+    isLoading.value = false;
+  }
+});
+</script>

+ 59 - 0
src/components/ui/button.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
+  {
+    variants: {
+      variant: {
+        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
+        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+        ghost: 'hover:bg-accent hover:text-accent-foreground',
+        link: 'text-primary underline-offset-4 hover:underline',
+        hero: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg',
+        heroOutline: 'border-2 border-primary text-primary hover:bg-primary/10'
+      },
+      size: {
+        default: 'h-10 px-4 py-2',
+        sm: 'h-9 rounded-md px-3',
+        lg: 'h-11 rounded-md px-8',
+        xl: 'h-14 rounded-lg px-8 text-lg',
+        icon: 'h-10 w-10',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+type ButtonVariants = VariantProps<typeof buttonVariants>
+
+interface Props {
+  variant?: ButtonVariants['variant']
+  size?: ButtonVariants['size']
+  as?: any
+  to?: string
+  href?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  as: 'button',
+})
+</script>
+
+<template>
+  <component
+    :is="as"
+    :to="to"
+    :href="href"
+    :class="cn(buttonVariants({ variant, size }), $attrs.class ?? '')"
+  >
+    <slot />
+  </component>
+</template>

+ 21 - 0
src/i18n.ts

@@ -0,0 +1,21 @@
+import { createI18n } from 'vue-i18n';
+import en from './locales/en.json';
+import me from './locales/me.json';
+import ru from './locales/ru.json';
+
+const i18n = createI18n({
+  legacy: false,
+  locale: 'en',
+  fallbackLocale: 'en',
+  messages: {
+    en,
+    me,
+    ru
+  }
+});
+
+export const setLanguage = (lang: string) => {
+  (i18n.global as any).locale.value = lang;
+};
+export const currentLanguage = () => (i18n.global as any).locale.value as string;
+export default i18n;

+ 174 - 0
src/index.css

@@ -0,0 +1,174 @@
+@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+  :root {
+    --background: 220 16% 8%;
+    --foreground: 220 10% 90%;
+
+    --card: 220 14% 12%;
+    --card-foreground: 220 10% 90%;
+
+    --popover: 220 14% 12%;
+    --popover-foreground: 220 10% 90%;
+
+    --primary: 210 50% 55%;
+    --primary-foreground: 220 16% 8%;
+
+    --secondary: 220 14% 16%;
+    --secondary-foreground: 220 10% 90%;
+
+    --muted: 220 12% 20%;
+    --muted-foreground: 220 10% 50%;
+
+    --accent: 210 40% 50%;
+    --accent-foreground: 220 16% 8%;
+
+    --destructive: 0 55% 55%;
+    --destructive-foreground: 220 10% 90%;
+
+    --border: 220 12% 20%;
+    --input: 220 12% 20%;
+    --ring: 210 50% 55%;
+
+    --radius: 0.75rem;
+
+    /* Custom design tokens */
+    --gradient-primary: linear-gradient(135deg, hsl(210 50% 55%) 0%, hsl(220 45% 65%) 100%);
+    --gradient-hero: linear-gradient(180deg, hsl(220 16% 8%) 0%, hsl(220 18% 13%) 50%, hsl(220 16% 8%) 100%);
+    --gradient-card: linear-gradient(145deg, hsl(220 14% 14%) 0%, hsl(220 14% 10%) 100%);
+    --gradient-glow: radial-gradient(ellipse at center, hsl(210 50% 55% / 0.1) 0%, transparent 70%);
+    
+    --shadow-glow: 0 0 30px hsl(210 50% 55% / 0.15);
+    --shadow-card: 0 8px 32px hsl(0 0% 0% / 0.4);
+    --shadow-hover: 0 12px 48px hsl(210 50% 55% / 0.1);
+
+    --font-display: 'Orbitron', sans-serif;
+    --font-body: 'Inter', sans-serif;
+
+    --sidebar-background: 220 14% 12%;
+    --sidebar-foreground: 220 10% 90%;
+    --sidebar-primary: 210 50% 55%;
+    --sidebar-primary-foreground: 220 16% 8%;
+    --sidebar-accent: 220 12% 20%;
+    --sidebar-accent-foreground: 220 10% 90%;
+    --sidebar-border: 220 12% 20%;
+    --sidebar-ring: 210 50% 55%;
+  }
+
+  .dark {
+    --background: 220 20% 6%;
+    --foreground: 210 40% 98%;
+  }
+}
+
+@layer base {
+  * {
+    @apply border-border;
+  }
+
+  body {
+    @apply bg-background text-foreground;
+    font-family: var(--font-body);
+  }
+
+  h1, h2, h3, h4, h5, h6 {
+    font-family: var(--font-display);
+  }
+}
+
+@layer utilities {
+  .text-gradient {
+    @apply bg-clip-text text-transparent;
+    background-image: var(--gradient-primary);
+  }
+
+  .bg-gradient-hero {
+    background: var(--gradient-hero);
+  }
+
+  .bg-gradient-card {
+    background: var(--gradient-card);
+  }
+
+  .bg-gradient-glow {
+    background: var(--gradient-glow);
+  }
+
+  .shadow-glow {
+    box-shadow: var(--shadow-glow);
+  }
+
+  .shadow-card {
+    box-shadow: var(--shadow-card);
+  }
+
+  .shadow-hover {
+    box-shadow: var(--shadow-hover);
+  }
+
+  .animate-float {
+    animation: float 6s ease-in-out infinite;
+  }
+
+  .animate-pulse-glow {
+    animation: pulse-glow 2s ease-in-out infinite;
+  }
+
+  .animate-slide-up {
+    animation: slide-up 0.6s ease-out forwards;
+  }
+
+  .animate-fade-in {
+    animation: fade-in 0.8s ease-out forwards;
+  }
+}
+
+@keyframes float {
+  0%, 100% {
+    transform: translateY(0px);
+  }
+  50% {
+    transform: translateY(-20px);
+  }
+}
+
+@keyframes pulse-glow {
+  0%, 100% {
+    box-shadow: 0 0 15px hsl(210 50% 55% / 0.2);
+  }
+  50% {
+    box-shadow: 0 0 30px hsl(210 50% 55% / 0.3);
+  }
+}
+
+@keyframes slide-up {
+  from {
+    opacity: 0;
+    transform: translateY(30px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes fade-in {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+/* Grid pattern overlay */
+.grid-pattern {
+  background-image: 
+    linear-gradient(hsl(210 50% 55% / 0.03) 1px, transparent 1px),
+    linear-gradient(90deg, hsl(210 50% 55% / 0.03) 1px, transparent 1px);
+  background-size: 50px 50px;
+}

+ 403 - 0
src/lib/api.ts

@@ -0,0 +1,403 @@
+import i18n from "../i18n";
+
+const API_BASE_URL = 'http://localhost:8000';
+
+const getErrorMessage = async (response: Response, defaultMsg: string) => {
+  try {
+    const errorData = await response.json();
+    if (errorData && errorData.detail) {
+      if (Array.isArray(errorData.detail)) {
+        // FastAPI/Pydantic validation error
+        const firstError = errorData.detail[0];
+        const errorType = firstError.type;
+        const fieldName = firstError.loc[firstError.loc.length - 1];
+        
+        // Try to translate the error type using i18n
+        // Pydantic types look like 'string_too_short', 'value_error.email'
+        const translationKey = `errors.${errorType}`;
+        const translatedMsg = i18n.global.t(translationKey, { 
+          ...firstError.ctx, 
+          defaultValue: firstError.msg 
+        });
+        
+        return `${fieldName}: ${translatedMsg}`;
+      }
+      return errorData.detail;
+    }
+  } catch (e) {}
+  return defaultMsg;
+};
+
+export const getMaterials = async () => {
+  const response = await fetch(`${API_BASE_URL}/materials?lang=${i18n.global.locale.value}`);
+  if (!response.ok) {
+    throw new Error('Failed to fetch materials');
+  }
+  return response.json();
+};
+
+export const getServices = async () => {
+  const response = await fetch(`${API_BASE_URL}/services?lang=${i18n.global.locale.value}`);
+  if (!response.ok) {
+    throw new Error('Failed to fetch services');
+  }
+  return response.json();
+};
+
+export const uploadFilesToServer = async (data: FormData) => {
+  const response = await fetch(`${API_BASE_URL}/files/upload`, {
+    method: "POST",
+    body: data,
+  });
+  if (!response.ok) throw new Error("Failed to upload files");
+  return response.json();
+};
+
+export const submitOrder = async (orderData: FormData) => {
+  const token = localStorage.getItem("token");
+  const headers: Record<string, string> = {};
+  if (token) {
+    headers['Authorization'] = `Bearer ${token}`;
+  }
+
+  const response = await fetch(`${API_BASE_URL}/orders?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    body: orderData,
+    headers: headers,
+    // Note: Do not set Content-Type header manually when using FormData
+    // The browser will automatically set it with the correct boundary
+  });
+  
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to submit order'));
+  }
+  
+  return response.json();
+};
+
+export const getMyOrders = async () => {
+  const token = localStorage.getItem("token");
+  if (!token) return [];
+
+  const response = await fetch(`${API_BASE_URL}/orders/my?lang=${i18n.global.locale.value}`, {
+    headers: {
+      'Authorization': `Bearer ${token}`
+    }
+  });
+  
+  if (!response.ok) {
+    throw new Error('Failed to fetch orders');
+  }
+  
+  return response.json();
+};
+
+export const registerUser = async (userData: any) => {
+  const response = await fetch(`${API_BASE_URL}/auth/register?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(userData),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to register'));
+  }
+  return response.json();
+};
+
+export const loginUser = async (userData: any) => {
+  const response = await fetch(`${API_BASE_URL}/auth/login?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(userData),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Incorrect credentials'));
+  }
+  return response.json();
+};
+
+export const socialLogin = async (socialData: any) => {
+  const response = await fetch(`${API_BASE_URL}/auth/social-login?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(socialData),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Social login failed'));
+  }
+  return response.json();
+};
+
+export const getCurrentUser = async () => {
+  const token = localStorage.getItem("token");
+  if (!token) return null;
+
+  const response = await fetch(`${API_BASE_URL}/auth/me?lang=${i18n.global.locale.value}`, {
+    headers: {
+      'Authorization': `Bearer ${token}`
+    }
+  });
+  
+  if (!response.ok) {
+    if (response.status === 401) {
+      localStorage.removeItem("token");
+    }
+    return null;
+  }
+  
+  return response.json();
+};
+
+export const updateProfile = async (userData: any) => {
+  const token = localStorage.getItem("token");
+  if (!token) throw new Error("No token found");
+
+  const response = await fetch(`${API_BASE_URL}/auth/me?lang=${i18n.global.locale.value}`, {
+    method: 'PUT',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(userData),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to update profile'));
+  }
+  return response.json();
+};
+
+export const forgotPassword = async (email: string) => {
+  const response = await fetch(`${API_BASE_URL}/auth/forgot-password?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify({ email }),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to request reset'));
+  }
+  return response.json();
+};
+
+export const resetPassword = async (data: any) => {
+  const response = await fetch(`${API_BASE_URL}/auth/reset-password?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(data),
+  });
+  if (!response.ok) {
+    throw new Error(await getErrorMessage(response, 'Failed to reset password'));
+  }
+  return response.json();
+};
+
+export const adminGetOrders = async () => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/orders?lang=${i18n.global.locale.value}`, {
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to fetch admin orders");
+  return response.json();
+};
+
+export const adminUpdateOrder = async (orderId: number, data: any) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/orders/${orderId}?lang=${i18n.global.locale.value}`, {
+    method: 'PATCH',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to update order");
+  return response.json();
+};
+
+export const getPriceEstimate = async (data: any) => {
+  const response = await fetch(`${API_BASE_URL}/orders/estimate?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to get estimate");
+  return response.json();
+};
+
+export const adminGetMaterials = async () => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/materials?lang=${i18n.global.locale.value}`, {
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to fetch admin materials");
+  return response.json();
+};
+
+export const adminCreateMaterial = async (data: any) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/materials?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to create material");
+  return response.json();
+};
+
+export const adminUpdateMaterial = async (id: number, data: any) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/materials/${id}?lang=${i18n.global.locale.value}`, {
+    method: 'PATCH',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to update material");
+  return response.json();
+};
+
+export const adminGetServices = async () => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/services?lang=${i18n.global.locale.value}`, {
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to fetch admin services");
+  return response.json();
+};
+
+export const adminCreateService = async (data: any) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/services?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to create service");
+  return response.json();
+};
+
+export const adminUpdateService = async (id: number, data: any) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/services/${id}?lang=${i18n.global.locale.value}`, {
+    method: 'PATCH',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to update service");
+  return response.json();
+};
+
+export const adminDeleteMaterial = async (id: number) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/materials/${id}?lang=${i18n.global.locale.value}`, {
+    method: 'DELETE',
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to delete material");
+  return response.json();
+};
+
+export const adminDeleteService = async (id: number) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/services/${id}?lang=${i18n.global.locale.value}`, {
+    method: 'DELETE',
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to delete service");
+  return response.json();
+};
+
+export const adminUploadOrderPhoto = async (orderId: number, formData: FormData) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/orders/${orderId}/photos?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Authorization': `Bearer ${token}`
+    },
+    body: formData
+  });
+  if (!response.ok) throw new Error("Failed to upload photo");
+  return response.json();
+};
+
+export const adminAttachFile = async (orderId: number, formData: FormData) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/orders/${orderId}/attach-file?lang=${i18n.global.locale.value}`, {
+    method: 'POST',
+    headers: { 
+      'Authorization': `Bearer ${token}`
+    },
+    body: formData
+  });
+  if (!response.ok) throw new Error("Failed to attach file");
+  return response.json();
+};
+
+export const adminUpdatePhotoStatus = async (photoId: number, data: { is_public: boolean }) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/admin/photos/${photoId}?lang=${i18n.global.locale.value}`, {
+    method: 'PATCH',
+    headers: { 
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify(data)
+  });
+  if (!response.ok) throw new Error("Failed to update photo status");
+  return response.json();
+};
+
+export const getPortfolio = async () => {
+  const response = await fetch(`${API_BASE_URL}/portfolio?lang=${i18n.global.locale.value}`);
+  if (!response.ok) throw new Error("Failed to fetch portfolio");
+  return response.json();
+};
+
+export const getOrderDetails = async (orderId: number) => {
+  const response = await fetch(`${API_BASE_URL}/orders/${orderId}?lang=${i18n.global.locale.value}`);
+  if (!response.ok) throw new Error("Failed to fetch order details");
+  return response.json();
+};
+
+export const getOrderMessages = async (orderId: number) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/orders/${orderId}/messages`, {
+    headers: { 'Authorization': `Bearer ${token}` }
+  });
+  if (!response.ok) throw new Error("Failed to fetch messages");
+  return response.json();
+};
+
+export const sendOrderMessage = async (orderId: number, message: string) => {
+  const token = localStorage.getItem("token");
+  const response = await fetch(`${API_BASE_URL}/orders/${orderId}/messages`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Authorization': `Bearer ${token}`
+    },
+    body: JSON.stringify({ message })
+  });
+  if (!response.ok) throw new Error("Failed to send message");
+  return response.json();
+};

+ 6 - 0
src/lib/utils.ts

@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs));
+}

+ 161 - 0
src/locales/en.json

@@ -0,0 +1,161 @@
+{
+  "auth": {
+    "back": "Back to Home",
+    "fields": {
+      "confirmPassword": "Confirm Password",
+      "email": "Email",
+      "password": "Password"
+    },
+    "forgot": {
+      "link": "Forgot Password?",
+      "submit": "Send Reset Link",
+      "subtitle": "Enter your email for reset instructions",
+      "title": "Forgot Password?",
+      "toggle": "Back to Login"
+    },
+    "login": {
+      "submit": "Log In",
+      "subtitle": "Log in to your Radionica 3D account",
+      "title": "Welcome Back",
+      "toggle": "New here? Create an account"
+    },
+    "register": {
+      "submit": "Create Account",
+      "subtitle": "Start printing your ideas today",
+      "title": "Join Us",
+      "toggle": "Already have an account? Log In"
+    },
+    "reset": {
+      "submit": "Reset Password",
+      "subtitle": "Choose a strong new password",
+      "title": "Reset Password",
+      "token": "Code from email"
+    }
+  },
+  "chat": {
+    "admin": "Support",
+    "empty": "No messages yet. Start a conversation!",
+    "open": "Chat",
+    "placeholder": "Type a message...",
+    "title": "Order Chat",
+    "unread": "New message"
+  },
+  "errors": {
+    "field_required": "This field is required",
+    "missing": "Field is required",
+    "string_too_short": "Too short, min {{min_length}} characters",
+    "too_short": "Field too short",
+    "unknown": "Something went wrong",
+    "value_error": {
+      "email": "Invalid email"
+    }
+  },
+  "footer": {
+    "about": "About Us",
+    "allRightsReserved": "All rights reserved.",
+    "api": "Documentation",
+    "blog": "Blog",
+    "careers": "Careers",
+    "company": "Company",
+    "contact": "Contact",
+    "guidelines": "Guidelines",
+    "help": "Help Center",
+    "materials": "Materials",
+    "privacy": "Privacy",
+    "services": "Services",
+    "support": "Support",
+    "tagline": "Radionica 3D — A service built on trust. We bring your ideas to life, you value our craftsmanship.",
+    "terms": "Terms"
+  },
+  "hero": {
+    "badge": "Trust in Every Layer",
+    "description": "A unique 3D printing service: send us a model, receive it by mail, and pay what you think it's worth.",
+    "pricingButton": "How It Works",
+    "stats": {
+      "materials": "Materials",
+      "materialsValue": "10+",
+      "precision": "Precision",
+      "precisionValue": "0.1mm",
+      "shipping": "Mail Delivery",
+      "shippingValue": "Express"
+    },
+    "title": "We Print —",
+    "titleGradient": "You Value",
+    "uploadButton": "Order Print"
+  },
+  "nav": {
+    "howItWorks": "How It Works",
+    "logIn": "Log In",
+    "logOut": "Log Out",
+    "materials": "Materials",
+    "myOrders": "My Orders",
+    "philosophy": "Our Philosophy",
+    "register": "Register",
+    "services": "Services"
+  },
+  "pricing": {
+    "badge": "Trust Policy",
+    "description": "No upfront costs or complex calculators. You only pay for results you value.",
+    "materials": "Available Materials",
+    "requestQuote": "Send Request",
+    "saveConfig": "Save",
+    "title": "Payment",
+    "titleGradient": "After Delivery",
+    "trustSteps": {
+      "step1": "Send us an STL model or a link",
+      "step2": "We'll craft it using the best material",
+      "step3": "Receive the package at your address",
+      "step4": "Evaluate our work and pay your price"
+    }
+  },
+  "services": {
+    "badge": "Our Capabilities",
+    "description": "We'll choose the optimal printing method for your specific design.",
+    "fdm": {
+      "description": "Durable parts made from engineering plastics.",
+      "title": "FDM Printing"
+    },
+    "sla": {
+      "description": "Maximum resolution and smooth industrial finish.",
+      "title": "SLA Resin"
+    },
+    "title": "Core",
+    "titleGradient": "Technologies"
+  },
+  "upload": {
+    "addressPlaceholder": "City, ZIP, Address (free form)",
+    "badge": "Place Your Project",
+    "browse": "browse files",
+    "continue": "Submit Request",
+    "description": "Upload a file or provide a link to a model (Thingiverse, Printables, etc.). We'll contact you for details.",
+    "dropzone": "Upload files (STL, OBJ, STEP)",
+    "dropzoneActive": "Drop your files here",
+    "email": "Email Address",
+    "firstName": "First Name",
+    "lastName": "Last Name",
+    "modelLink": "Model Link (optional)",
+    "modelLinkPlaceholder": "https://www.printables.com/model/...",
+    "notes": "Order Notes / Remarks",
+    "notesPlaceholder": "Color preferences, specific requirements, or special instructions...",
+    "phone": "Phone Number",
+    "quantity": "Number of Copies",
+    "shippingAddress": "Shipping Address",
+    "submitting": "Sending...",
+    "success": "Order submitted successfully! We will contact you soon.",
+    "title": "Submit",
+    "titleGradient": "Your Idea",
+    "uploadedFiles": "Selected Files"
+  },
+  "whyTrust": {
+    "description1": "We believe that high-quality 3D printing should be accessible, and the process as simple as possible. Our experience allows us to take on the risks: we are confident in our equipment and the quality of our materials.",
+    "description2": "This approach removes the barriers of 'complex calculations' and gives you the opportunity to get exactly what you intended, evaluating the results yourself.",
+    "items": {
+      "noCommissions": "No fees",
+      "noPrepayment": "No prepayment",
+      "shipping": "Mail delivery",
+      "yourPrice": "Your price"
+    },
+    "title": "Why we",
+    "titleItalic": "trust"
+  }
+}

+ 161 - 0
src/locales/me.json

@@ -0,0 +1,161 @@
+{
+  "auth": {
+    "back": "Nazad na početnu",
+    "fields": {
+      "confirmPassword": "Potvrdi lozinku",
+      "email": "Email",
+      "password": "Lozinka"
+    },
+    "forgot": {
+      "link": "Zaboravljena lozinka?",
+      "submit": "Pošalji link",
+      "subtitle": "Unesi svoj email i poslaćemo ti link",
+      "title": "Zaboravio si lozinku?",
+      "toggle": "Nazad na prijavu"
+    },
+    "login": {
+      "submit": "Prijavi se",
+      "subtitle": "Prijavi se na svoj Radionica 3D nalog",
+      "title": "Dobrodošao nazad",
+      "toggle": "Nemaš nalog? Registruj se"
+    },
+    "register": {
+      "submit": "Registruj se",
+      "subtitle": "Počni da štampaš svoje ideje danas",
+      "title": "Kreiraj nalog",
+      "toggle": "Već imaš nalog? Prijavi se"
+    },
+    "reset": {
+      "submit": "Potvrdi novu lozinku",
+      "subtitle": "Kreiraj novu sigurnu lozinku",
+      "title": "Nova lozinka",
+      "token": "Kod iz mejla"
+    }
+  },
+  "chat": {
+    "admin": "Podrška",
+    "empty": "Još nema poruka. Započnite razgovor!",
+    "open": "Čat",
+    "placeholder": "Upišite poruku...",
+    "title": "Čat za narudžbu",
+    "unread": "Nova poruka"
+  },
+  "errors": {
+    "field_required": "Ovo polje je obavezno",
+    "missing": "Ovo polje je obavezno",
+    "string_too_short": "Previše kratko, min {{min_length}} karaktera",
+    "too_short": "Polje je previše kratko",
+    "unknown": "Nešto je pošlo po zlu",
+    "value_error": {
+      "email": "Neispravan email"
+    }
+  },
+  "footer": {
+    "about": "O nama",
+    "allRightsReserved": "Sva prava zadržana.",
+    "api": "Dokumentacija",
+    "blog": "Blog",
+    "careers": "Karijere",
+    "company": "Kompanija",
+    "contact": "Kontakt",
+    "guidelines": "Uputstva",
+    "help": "Centar za pomoć",
+    "materials": "Materijali",
+    "privacy": "Privatnost",
+    "services": "Usluge",
+    "support": "Podrška",
+    "tagline": "Radionica 3D — Servis izgrađen na povjerenju. Mi oživljavamo tvoje ideje, ti procjenjuješ naš rad.",
+    "terms": "Uslovi"
+  },
+  "hero": {
+    "badge": "Povjerenje u svakom sloju",
+    "description": "Jedinstveni servis 3D štampe: pošaljite model, dobijte gotov proizvod poštom i platite onoliko koliko smatrate da vrijedi.",
+    "pricingButton": "Kako funkcioniše",
+    "stats": {
+      "materials": "Materijala",
+      "materialsValue": "10+",
+      "precision": "Preciznost",
+      "precisionValue": "0.1mm",
+      "shipping": "Dostava poštom",
+      "shippingValue": "Ekspres"
+    },
+    "title": "Mi štampamo —",
+    "titleGradient": "Vi procjenjujete",
+    "uploadButton": "Naruči štampu"
+  },
+  "nav": {
+    "howItWorks": "Kako funkcioniše",
+    "logIn": "Prijavi se",
+    "logOut": "Odjavi se",
+    "materials": "Materijali",
+    "myOrders": "Moji nalozi",
+    "philosophy": "Naš pristup",
+    "register": "Registruj se",
+    "services": "Usluge"
+  },
+  "pricing": {
+    "badge": "Politika povjerenja",
+    "description": "Bez uplate unaprijed i komplikovanih kalkulatora. Plaćaš samo za rezultat u koji vjeruješ.",
+    "materials": "Dostupni materijali",
+    "requestQuote": "Pošalji zahtjev",
+    "saveConfig": "Sačuvaj",
+    "title": "Plaćanje",
+    "titleGradient": "nakon isporuke",
+    "trustSteps": {
+      "step1": "Pošaljite nam STL model ili link",
+      "step2": "Mi ćemo ga izraditi od najboljeg materijala",
+      "step3": "Primite paket na navedenu adresu",
+      "step4": "Procijenite naš rad i platite svoju cijenu"
+    }
+  },
+  "services": {
+    "badge": "Naše mogućnosti",
+    "description": "Odabraćemo optimalnu metodu štampe za tvoj specifični dizajn.",
+    "fdm": {
+      "description": "Izdržljivi djelovi od industrijske plastike.",
+      "title": "FDM Štampa"
+    },
+    "sla": {
+      "description": "Maksimalna preciznost i glatka industrijska obrada.",
+      "title": "SLA Resin"
+    },
+    "title": "Glavne",
+    "titleGradient": "tehnologije"
+  },
+  "upload": {
+    "addressPlaceholder": "Grad, Poštanski broj, Adresa (slobodna forma)",
+    "badge": "Kreiranje projekta",
+    "browse": "pretraži datoteke",
+    "continue": "Pošalji zahtjev",
+    "description": "Otpremite datoteku ili navedite link do modela (Thingiverse, Printables i dr.). Kontaktiraćemo vas radi detalja.",
+    "dropzone": "Otpremi datoteke (STL, OBJ, STEP)",
+    "dropzoneActive": "Prevucite datoteke ovdje",
+    "email": "Email adresa",
+    "firstName": "Ime",
+    "lastName": "Prezime",
+    "modelLink": "Link do modela (opciono)",
+    "modelLinkPlaceholder": "https://www.printables.com/model/...",
+    "notes": "Napomene uz narudžbu",
+    "notesPlaceholder": "Želje za bojom, materijalom, specifičnim zahtjevima ili posebne instrukcije...",
+    "phone": "Broj telefona",
+    "quantity": "Broj kopija",
+    "shippingAddress": "Adresa isporuke",
+    "submitting": "Slanje...",
+    "success": "Zahtjev je uspješno poslat! Kontaktiraćemo vas uskoro.",
+    "title": "Pošaljite",
+    "titleGradient": "vašu ideju",
+    "uploadedFiles": "Odabrane datoteke"
+  },
+  "whyTrust": {
+    "description1": "Vjerujemo da kvalitetna 3D štampa treba da bude dostupna, a proces — maksimalno jednostavan. Naše iskustvo nam omogućava da preuzmemo rizike: sigurni smo u našu opremu i kvalitet materijala.",
+    "description2": "Ovaj pristup eliminiše barijere \"komplikovanih proračuna\" i daje vam mogućnost da dobijete upravo ono što ste zamislili, procjenjujući rezultat samostalno.",
+    "items": {
+      "noCommissions": "Bez provizija",
+      "noPrepayment": "Bez uplate unaprijed",
+      "shipping": "Isporuka poštom",
+      "yourPrice": "Tvoja cijena"
+    },
+    "title": "Zašto nam",
+    "titleItalic": "vjeruju"
+  }
+}

+ 161 - 0
src/locales/ru.json

@@ -0,0 +1,161 @@
+{
+  "auth": {
+    "back": "На главную",
+    "fields": {
+      "confirmPassword": "Подтвердите пароль",
+      "email": "Email",
+      "password": "Пароль"
+    },
+    "forgot": {
+      "link": "Забыли пароль?",
+      "submit": "Отправить ссылку",
+      "subtitle": "Введите email, и мы отправим ссылку",
+      "title": "Забыли пароль?",
+      "toggle": "Вернуться к входу"
+    },
+    "login": {
+      "submit": "Войти",
+      "subtitle": "Войдите в свой аккаунт Radionica 3D",
+      "title": "С возвращением",
+      "toggle": "Нет аккаунта? Зарегистрируйтесь"
+    },
+    "register": {
+      "submit": "Зарегистрироваться",
+      "subtitle": "Начните печатать свои идеи сегодня",
+      "title": "Создать аккаунт",
+      "toggle": "Уже есть аккаунт? Войдите"
+    },
+    "reset": {
+      "submit": "Сбросить пароль",
+      "subtitle": "Придумайте новый надежный пароль",
+      "title": "Сброс пароля",
+      "token": "Код из письма"
+    }
+  },
+  "chat": {
+    "admin": "Поддержка",
+    "empty": "Сообщений пока нет. Начните диалог!",
+    "open": "Чат",
+    "placeholder": "Напишите сообщение...",
+    "title": "Чат по заказу",
+    "unread": "Новое сообщение"
+  },
+  "errors": {
+    "field_required": "Это поле обязательно для заполнения",
+    "missing": "Обязательное поле",
+    "string_too_short": "Слишком коротко, минимум {{min_length}} символов",
+    "too_short": "Поле слишком короткое",
+    "unknown": "Что-то пошло не так",
+    "value_error": {
+      "email": "Некорректный email"
+    }
+  },
+  "footer": {
+    "about": "О нас",
+    "allRightsReserved": "Все права защищены.",
+    "api": "Документация",
+    "blog": "Блог",
+    "careers": "Вакансии",
+    "company": "Компания",
+    "contact": "Контакты",
+    "guidelines": "Руководство",
+    "help": "Справочный центр",
+    "materials": "Материалы",
+    "privacy": "Конфиденциальность",
+    "services": "Услуги",
+    "support": "Поддержка",
+    "tagline": "Radionica 3D — сервис, построенный на доверии. Мы воплощаем ваши идеи, вы оцениваете наш труд.",
+    "terms": "Условия"
+  },
+  "hero": {
+    "badge": "Доверие в каждом слое",
+    "description": "Уникальный сервис 3D-печати: пришлите модель, получите готовое изделие по почте и заплатите столько, сколько посчитаете нужным.",
+    "pricingButton": "Как это работает",
+    "stats": {
+      "materials": "Материалов",
+      "materialsValue": "10+",
+      "precision": "Точность",
+      "precisionValue": "0.1мм",
+      "shipping": "Доставка почтой",
+      "shippingValue": "Экспресс"
+    },
+    "title": "Мы печатаем —",
+    "titleGradient": "Вы оцениваете",
+    "uploadButton": "Заказать печать"
+  },
+  "nav": {
+    "howItWorks": "Как это работает",
+    "logIn": "Войти",
+    "logOut": "Выйти",
+    "materials": "Материалы",
+    "myOrders": "Мои заказы",
+    "philosophy": "Наш подход",
+    "register": "Регистрация",
+    "services": "Услуги"
+  },
+  "pricing": {
+    "badge": "Политика доверия",
+    "description": "Никаких предоплат и сложных калькуляторов. Вы платите только за результат, в который верите.",
+    "materials": "Доступные материалы",
+    "requestQuote": "Отправить запрос",
+    "saveConfig": "Сохранить",
+    "title": "Оплата",
+    "titleGradient": "после получения",
+    "trustSteps": {
+      "step1": "Отправьте нам STL модель или ссылку",
+      "step2": "Мы изготовим ее из подходящего материала",
+      "step3": "Получите посылку на указанный адрес",
+      "step4": "Оцените работу и оплатите удобным способом"
+    }
+  },
+  "services": {
+    "badge": "Наши возможности",
+    "description": "Мы подберем оптимальный метод печати для вашей задачи.",
+    "fdm": {
+      "description": "Прочные детали из инженерных пластиков.",
+      "title": "FDM печать"
+    },
+    "sla": {
+      "description": "Максимальная детализация и гладкость изделий.",
+      "title": "SLA смола"
+    },
+    "title": "Технологии",
+    "titleGradient": "реализации"
+  },
+  "upload": {
+    "addressPlaceholder": "Город, Индекс, Адрес (в свободной форме)",
+    "badge": "Оформление заказа",
+    "browse": "выбрать файлы",
+    "continue": "Отправить заказ",
+    "description": "Загрузите файл или укажите ссылку на модель (Thingiverse, Printables и др.). Мы свяжемся с вами для уточнения деталей.",
+    "dropzone": "Загрузить файлы (STL, OBJ, STEP)",
+    "dropzoneActive": "Переместите файлы сюда",
+    "email": "Email",
+    "firstName": "Имя",
+    "lastName": "Фамилия",
+    "modelLink": "Ссылка на модель (необязательно)",
+    "modelLinkPlaceholder": "https://www.printables.com/model/...",
+    "notes": "Примечания к заказу",
+    "notesPlaceholder": "Пожелания по цвету, материалу, толщине стенок или другие инструкции...",
+    "phone": "Телефон",
+    "quantity": "Количество копий",
+    "shippingAddress": "Адрес доставки",
+    "submitting": "Отправка...",
+    "success": "Заказ успешно отправлен! Мы свяжемся с вами в ближайшее время.",
+    "title": "Пришлите",
+    "titleGradient": "вашу идею",
+    "uploadedFiles": "Выбранные файлы"
+  },
+  "whyTrust": {
+    "description1": "Мы верим, что качественная 3D-печать должна быть доступной, а процесс — максимально простым. Наш опыт позволяет нам брать на себя риски: мы уверены в своем оборудовании и качестве материалов.",
+    "description2": "Этот подход позволяет убрать барьеры \"сложных расчетов\" и дать вам возможность получить именно то, что вы задумали, оценив результат самостоятельно.",
+    "items": {
+      "noCommissions": "Без комиссий",
+      "noPrepayment": "Без предоплаты",
+      "shipping": "Отправка почтой",
+      "yourPrice": "Ваша цена"
+    },
+    "title": "Почему мы",
+    "titleItalic": "доверяем"
+  }
+}

+ 629 - 0
src/locales/translations.json

@@ -0,0 +1,629 @@
+{
+  "auth": {
+    "back": {
+      "en": "Back to Home",
+      "me": "Nazad na početnu",
+      "ru": "На главную"
+    },
+    "fields": {
+      "confirmPassword": {
+        "en": "Confirm Password",
+        "me": "Potvrdi lozinku",
+        "ru": "Подтвердите пароль"
+      },
+      "email": {
+        "en": "Email",
+        "me": "Email",
+        "ru": "Email"
+      },
+      "password": {
+        "en": "Password",
+        "me": "Lozinka",
+        "ru": "Пароль"
+      }
+    },
+    "forgot": {
+      "link": {
+        "en": "Forgot Password?",
+        "me": "Zaboravljena lozinka?",
+        "ru": "Забыли пароль?"
+      },
+      "submit": {
+        "en": "Send Reset Link",
+        "me": "Pošalji link",
+        "ru": "Отправить ссылку"
+      },
+      "subtitle": {
+        "en": "Enter your email for reset instructions",
+        "me": "Unesi svoj email i poslaćemo ti link",
+        "ru": "Введите email, и мы отправим ссылку"
+      },
+      "title": {
+        "en": "Forgot Password?",
+        "me": "Zaboravio si lozinku?",
+        "ru": "Забыли пароль?"
+      },
+      "toggle": {
+        "en": "Back to Login",
+        "me": "Nazad na prijavu",
+        "ru": "Вернуться к входу"
+      }
+    },
+    "login": {
+      "submit": {
+        "en": "Log In",
+        "me": "Prijavi se",
+        "ru": "Войти"
+      },
+      "subtitle": {
+        "en": "Log in to your Radionica 3D account",
+        "me": "Prijavi se na svoj Radionica 3D nalog",
+        "ru": "Войдите в свой аккаунт Radionica 3D"
+      },
+      "title": {
+        "en": "Welcome Back",
+        "me": "Dobrodošao nazad",
+        "ru": "С возвращением"
+      },
+      "toggle": {
+        "en": "New here? Create an account",
+        "me": "Nemaš nalog? Registruj se",
+        "ru": "Нет аккаунта? Зарегистрируйтесь"
+      }
+    },
+    "register": {
+      "submit": {
+        "en": "Create Account",
+        "me": "Registruj se",
+        "ru": "Зарегистрироваться"
+      },
+      "subtitle": {
+        "en": "Start printing your ideas today",
+        "me": "Počni da štampaš svoje ideje danas",
+        "ru": "Начните печатать свои идеи сегодня"
+      },
+      "title": {
+        "en": "Join Us",
+        "me": "Kreiraj nalog",
+        "ru": "Создать аккаунт"
+      },
+      "toggle": {
+        "en": "Already have an account? Log In",
+        "me": "Već imaš nalog? Prijavi se",
+        "ru": "Уже есть аккаунт? Войдите"
+      }
+    },
+    "reset": {
+      "submit": {
+        "en": "Reset Password",
+        "me": "Potvrdi novu lozinku",
+        "ru": "Сбросить пароль"
+      },
+      "subtitle": {
+        "en": "Choose a strong new password",
+        "me": "Kreiraj novu sigurnu lozinku",
+        "ru": "Придумайте новый надежный пароль"
+      },
+      "title": {
+        "en": "Reset Password",
+        "me": "Nova lozinka",
+        "ru": "Сброс пароля"
+      },
+      "token": {
+        "en": "Code from email",
+        "me": "Kod iz mejla",
+        "ru": "Код из письма"
+      }
+    }
+  },
+  "chat": {
+    "admin": {
+      "en": "Support",
+      "me": "Podrška",
+      "ru": "Поддержка"
+    },
+    "empty": {
+      "en": "No messages yet. Start a conversation!",
+      "me": "Još nema poruka. Započnite razgovor!",
+      "ru": "Сообщений пока нет. Начните диалог!"
+    },
+    "open": {
+      "en": "Chat",
+      "me": "Čat",
+      "ru": "Чат"
+    },
+    "placeholder": {
+      "en": "Type a message...",
+      "me": "Upišite poruku...",
+      "ru": "Напишите сообщение..."
+    },
+    "title": {
+      "en": "Order Chat",
+      "me": "Čat za narudžbu",
+      "ru": "Чат по заказу"
+    },
+    "unread": {
+      "en": "New message",
+      "me": "Nova poruka",
+      "ru": "Новое сообщение"
+    }
+  },
+  "errors": {
+    "field_required": {
+      "en": "This field is required",
+      "me": "Ovo polje je obavezno",
+      "ru": "Это поле обязательно для заполнения"
+    },
+    "missing": {
+      "en": "Field is required",
+      "me": "Ovo polje je obavezno",
+      "ru": "Обязательное поле"
+    },
+    "string_too_short": {
+      "en": "Too short, min {{min_length}} characters",
+      "me": "Previše kratko, min {{min_length}} karaktera",
+      "ru": "Слишком коротко, минимум {{min_length}} символов"
+    },
+    "too_short": {
+      "en": "Field too short",
+      "me": "Polje je previše kratko",
+      "ru": "Поле слишком короткое"
+    },
+    "unknown": {
+      "en": "Something went wrong",
+      "me": "Nešto je pošlo po zlu",
+      "ru": "Что-то пошло не так"
+    },
+    "value_error": {
+      "email": {
+        "en": "Invalid email",
+        "me": "Neispravan email",
+        "ru": "Некорректный email"
+      }
+    }
+  },
+  "footer": {
+    "about": {
+      "en": "About Us",
+      "me": "O nama",
+      "ru": "О нас"
+    },
+    "allRightsReserved": {
+      "en": "All rights reserved.",
+      "me": "Sva prava zadržana.",
+      "ru": "Все права защищены."
+    },
+    "api": {
+      "en": "Documentation",
+      "me": "Dokumentacija",
+      "ru": "Документация"
+    },
+    "blog": {
+      "en": "Blog",
+      "me": "Blog",
+      "ru": "Блог"
+    },
+    "careers": {
+      "en": "Careers",
+      "me": "Karijere",
+      "ru": "Вакансии"
+    },
+    "company": {
+      "en": "Company",
+      "me": "Kompanija",
+      "ru": "Компания"
+    },
+    "contact": {
+      "en": "Contact",
+      "me": "Kontakt",
+      "ru": "Контакты"
+    },
+    "guidelines": {
+      "en": "Guidelines",
+      "me": "Uputstva",
+      "ru": "Руководство"
+    },
+    "help": {
+      "en": "Help Center",
+      "me": "Centar za pomoć",
+      "ru": "Справочный центр"
+    },
+    "materials": {
+      "en": "Materials",
+      "me": "Materijali",
+      "ru": "Материалы"
+    },
+    "privacy": {
+      "en": "Privacy",
+      "me": "Privatnost",
+      "ru": "Конфиденциальность"
+    },
+    "services": {
+      "en": "Services",
+      "me": "Usluge",
+      "ru": "Услуги"
+    },
+    "support": {
+      "en": "Support",
+      "me": "Podrška",
+      "ru": "Поддержка"
+    },
+    "tagline": {
+      "en": "Radionica 3D — A service built on trust. We bring your ideas to life, you value our craftsmanship.",
+      "me": "Radionica 3D — Servis izgrađen na povjerenju. Mi oživljavamo tvoje ideje, ti procjenjuješ naš rad.",
+      "ru": "Radionica 3D — сервис, построенный на доверии. Мы воплощаем ваши идеи, вы оцениваете наш труд."
+    },
+    "terms": {
+      "en": "Terms",
+      "me": "Uslovi",
+      "ru": "Условия"
+    }
+  },
+  "hero": {
+    "badge": {
+      "en": "Trust in Every Layer",
+      "me": "Povjerenje u svakom sloju",
+      "ru": "Доверие в каждом слое"
+    },
+    "description": {
+      "en": "A unique 3D printing service: send us a model, receive it by mail, and pay what you think it's worth.",
+      "me": "Jedinstveni servis 3D štampe: pošaljite model, dobijte gotov proizvod poštom i platite onoliko koliko smatrate da vrijedi.",
+      "ru": "Уникальный сервис 3D-печати: пришлите модель, получите готовое изделие по почте и заплатите столько, сколько посчитаете нужным."
+    },
+    "pricingButton": {
+      "en": "How It Works",
+      "me": "Kako funkcioniše",
+      "ru": "Как это работает"
+    },
+    "stats": {
+      "materials": {
+        "en": "Materials",
+        "me": "Materijala",
+        "ru": "Материалов"
+      },
+      "materialsValue": {
+        "en": "10+",
+        "me": "10+",
+        "ru": "10+"
+      },
+      "precision": {
+        "en": "Precision",
+        "me": "Preciznost",
+        "ru": "Точность"
+      },
+      "precisionValue": {
+        "en": "0.1mm",
+        "me": "0.1mm",
+        "ru": "0.1мм"
+      },
+      "shipping": {
+        "en": "Mail Delivery",
+        "me": "Dostava poštom",
+        "ru": "Доставка почтой"
+      },
+      "shippingValue": {
+        "en": "Express",
+        "me": "Ekspres",
+        "ru": "Экспресс"
+      }
+    },
+    "title": {
+      "en": "We Print —",
+      "me": "Mi štampamo —",
+      "ru": "Мы печатаем —"
+    },
+    "titleGradient": {
+      "en": "You Value",
+      "me": "Vi procjenjujete",
+      "ru": "Вы оцениваете"
+    },
+    "uploadButton": {
+      "en": "Order Print",
+      "me": "Naruči štampu",
+      "ru": "Заказать печать"
+    }
+  },
+  "nav": {
+    "howItWorks": {
+      "en": "How It Works",
+      "me": "Kako funkcioniše",
+      "ru": "Как это работает"
+    },
+    "logIn": {
+      "en": "Log In",
+      "me": "Prijavi se",
+      "ru": "Войти"
+    },
+    "logOut": {
+      "en": "Log Out",
+      "me": "Odjavi se",
+      "ru": "Выйти"
+    },
+    "materials": {
+      "en": "Materials",
+      "me": "Materijali",
+      "ru": "Материалы"
+    },
+    "myOrders": {
+      "en": "My Orders",
+      "me": "Moji nalozi",
+      "ru": "Мои заказы"
+    },
+    "philosophy": {
+      "en": "Our Philosophy",
+      "me": "Naš pristup",
+      "ru": "Наш подход"
+    },
+    "register": {
+      "en": "Register",
+      "me": "Registruj se",
+      "ru": "Регистрация"
+    },
+    "services": {
+      "en": "Services",
+      "me": "Usluge",
+      "ru": "Услуги"
+    }
+  },
+  "pricing": {
+    "badge": {
+      "en": "Trust Policy",
+      "me": "Politika povjerenja",
+      "ru": "Политика доверия"
+    },
+    "description": {
+      "en": "No upfront costs or complex calculators. You only pay for results you value.",
+      "me": "Bez uplate unaprijed i komplikovanih kalkulatora. Plaćaš samo za rezultat u koji vjeruješ.",
+      "ru": "Никаких предоплат и сложных калькуляторов. Вы платите только за результат, в который верите."
+    },
+    "materials": {
+      "en": "Available Materials",
+      "me": "Dostupni materijali",
+      "ru": "Доступные материалы"
+    },
+    "requestQuote": {
+      "en": "Send Request",
+      "me": "Pošalji zahtjev",
+      "ru": "Отправить запрос"
+    },
+    "saveConfig": {
+      "en": "Save",
+      "me": "Sačuvaj",
+      "ru": "Сохранить"
+    },
+    "title": {
+      "en": "Payment",
+      "me": "Plaćanje",
+      "ru": "Оплата"
+    },
+    "titleGradient": {
+      "en": "After Delivery",
+      "me": "nakon isporuke",
+      "ru": "после получения"
+    },
+    "trustSteps": {
+      "step1": {
+        "en": "Send us an STL model or a link",
+        "me": "Pošaljite nam STL model ili link",
+        "ru": "Отправьте нам STL модель или ссылку"
+      },
+      "step2": {
+        "en": "We'll craft it using the best material",
+        "me": "Mi ćemo ga izraditi od najboljeg materijala",
+        "ru": "Мы изготовим ее из подходящего материала"
+      },
+      "step3": {
+        "en": "Receive the package at your address",
+        "me": "Primite paket na navedenu adresu",
+        "ru": "Получите посылку на указанный адрес"
+      },
+      "step4": {
+        "en": "Evaluate our work and pay your price",
+        "me": "Procijenite naš rad i platite svoju cijenu",
+        "ru": "Оцените работу и оплатите удобным способом"
+      }
+    }
+  },
+  "services": {
+    "badge": {
+      "en": "Our Capabilities",
+      "me": "Naše mogućnosti",
+      "ru": "Наши возможности"
+    },
+    "description": {
+      "en": "We'll choose the optimal printing method for your specific design.",
+      "me": "Odabraćemo optimalnu metodu štampe za tvoj specifični dizajn.",
+      "ru": "Мы подберем оптимальный метод печати для вашей задачи."
+    },
+    "fdm": {
+      "description": {
+        "en": "Durable parts made from engineering plastics.",
+        "me": "Izdržljivi djelovi od industrijske plastike.",
+        "ru": "Прочные детали из инженерных пластиков."
+      },
+      "title": {
+        "en": "FDM Printing",
+        "me": "FDM Štampa",
+        "ru": "FDM печать"
+      }
+    },
+    "sla": {
+      "description": {
+        "en": "Maximum resolution and smooth industrial finish.",
+        "me": "Maksimalna preciznost i glatka industrijska obrada.",
+        "ru": "Максимальная детализация и гладкость изделий."
+      },
+      "title": {
+        "en": "SLA Resin",
+        "me": "SLA Resin",
+        "ru": "SLA смола"
+      }
+    },
+    "title": {
+      "en": "Core",
+      "me": "Glavne",
+      "ru": "Технологии"
+    },
+    "titleGradient": {
+      "en": "Technologies",
+      "me": "tehnologije",
+      "ru": "реализации"
+    }
+  },
+  "upload": {
+    "addressPlaceholder": {
+      "en": "City, ZIP, Address (free form)",
+      "me": "Grad, Poštanski broj, Adresa (slobodna forma)",
+      "ru": "Город, Индекс, Адрес (в свободной форме)"
+    },
+    "badge": {
+      "en": "Place Your Project",
+      "me": "Kreiranje projekta",
+      "ru": "Оформление заказа"
+    },
+    "browse": {
+      "en": "browse files",
+      "me": "pretraži datoteke",
+      "ru": "выбрать файлы"
+    },
+    "continue": {
+      "en": "Submit Request",
+      "me": "Pošalji zahtjev",
+      "ru": "Отправить заказ"
+    },
+    "description": {
+      "en": "Upload a file or provide a link to a model (Thingiverse, Printables, etc.). We'll contact you for details.",
+      "me": "Otpremite datoteku ili navedite link do modela (Thingiverse, Printables i dr.). Kontaktiraćemo vas radi detalja.",
+      "ru": "Загрузите файл или укажите ссылку на модель (Thingiverse, Printables и др.). Мы свяжемся с вами для уточнения деталей."
+    },
+    "dropzone": {
+      "en": "Upload files (STL, OBJ, STEP)",
+      "me": "Otpremi datoteke (STL, OBJ, STEP)",
+      "ru": "Загрузить файлы (STL, OBJ, STEP)"
+    },
+    "dropzoneActive": {
+      "en": "Drop your files here",
+      "me": "Prevucite datoteke ovdje",
+      "ru": "Переместите файлы сюда"
+    },
+    "email": {
+      "en": "Email Address",
+      "me": "Email adresa",
+      "ru": "Email"
+    },
+    "firstName": {
+      "en": "First Name",
+      "me": "Ime",
+      "ru": "Имя"
+    },
+    "lastName": {
+      "en": "Last Name",
+      "me": "Prezime",
+      "ru": "Фамилия"
+    },
+    "modelLink": {
+      "en": "Model Link (optional)",
+      "me": "Link do modela (opciono)",
+      "ru": "Ссылка на модель (необязательно)"
+    },
+    "modelLinkPlaceholder": {
+      "en": "https://www.printables.com/model/...",
+      "me": "https://www.printables.com/model/...",
+      "ru": "https://www.printables.com/model/..."
+    },
+    "notes": {
+      "en": "Order Notes / Remarks",
+      "me": "Napomene uz narudžbu",
+      "ru": "Примечания к заказу"
+    },
+    "notesPlaceholder": {
+      "en": "Color preferences, specific requirements, or special instructions...",
+      "me": "Želje za bojom, materijalom, specifičnim zahtjevima ili posebne instrukcije...",
+      "ru": "Пожелания по цвету, материалу, толщине стенок или другие инструкции..."
+    },
+    "phone": {
+      "en": "Phone Number",
+      "me": "Broj telefona",
+      "ru": "Телефон"
+    },
+    "quantity": {
+      "en": "Number of Copies",
+      "me": "Broj kopija",
+      "ru": "Количество копий"
+    },
+    "shippingAddress": {
+      "en": "Shipping Address",
+      "me": "Adresa isporuke",
+      "ru": "Адрес доставки"
+    },
+    "submitting": {
+      "en": "Sending...",
+      "me": "Slanje...",
+      "ru": "Отправка..."
+    },
+    "success": {
+      "en": "Order submitted successfully! We will contact you soon.",
+      "me": "Zahtjev je uspješno poslat! Kontaktiraćemo vas uskoro.",
+      "ru": "Заказ успешно отправлен! Мы свяжемся с вами в ближайшее время."
+    },
+    "title": {
+      "en": "Submit",
+      "me": "Pošaljite",
+      "ru": "Пришлите"
+    },
+    "titleGradient": {
+      "en": "Your Idea",
+      "me": "vašu ideju",
+      "ru": "вашу идею"
+    },
+    "uploadedFiles": {
+      "en": "Selected Files",
+      "me": "Odabrane datoteke",
+      "ru": "Выбранные файлы"
+    }
+  },
+  "whyTrust": {
+    "description1": {
+      "en": "We believe that high-quality 3D printing should be accessible, and the process as simple as possible. Our experience allows us to take on the risks: we are confident in our equipment and the quality of our materials.",
+      "me": "Vjerujemo da kvalitetna 3D štampa treba da bude dostupna, a proces — maksimalno jednostavan. Naše iskustvo nam omogućava da preuzmemo rizike: sigurni smo u našu opremu i kvalitet materijala.",
+      "ru": "Мы верим, что качественная 3D-печать должна быть доступной, а процесс — максимально простым. Наш опыт позволяет нам брать на себя риски: мы уверены в своем оборудовании и качестве материалов."
+    },
+    "description2": {
+      "en": "This approach removes the barriers of 'complex calculations' and gives you the opportunity to get exactly what you intended, evaluating the results yourself.",
+      "me": "Ovaj pristup eliminiše barijere \"komplikovanih proračuna\" i daje vam mogućnost da dobijete upravo ono što ste zamislili, procjenjujući rezultat samostalno.",
+      "ru": "Этот подход позволяет убрать барьеры \"сложных расчетов\" и дать вам возможность получить именно то, что вы задумали, оценив результат самостоятельно."
+    },
+    "items": {
+      "noCommissions": {
+        "en": "No fees",
+        "me": "Bez provizija",
+        "ru": "Без комиссий"
+      },
+      "noPrepayment": {
+        "en": "No prepayment",
+        "me": "Bez uplate unaprijed",
+        "ru": "Без предоплаты"
+      },
+      "shipping": {
+        "en": "Mail delivery",
+        "me": "Isporuka poštom",
+        "ru": "Отправка почтой"
+      },
+      "yourPrice": {
+        "en": "Your price",
+        "me": "Tvoja cijena",
+        "ru": "Ваша цена"
+      }
+    },
+    "title": {
+      "en": "Why we",
+      "me": "Zašto nam",
+      "ru": "Почему мы"
+    },
+    "titleItalic": {
+      "en": "trust",
+      "me": "vjeruju",
+      "ru": "доверяем"
+    }
+  }
+}

+ 14 - 0
src/main.ts

@@ -0,0 +1,14 @@
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import App from "./App.vue";
+import router from "./router";
+import i18n from "./i18n";
+import "./index.css";
+
+const app = createApp(App);
+
+app.use(createPinia());
+app.use(router);
+app.use(i18n);
+
+app.mount("#root");

+ 473 - 0
src/pages/Admin.vue

@@ -0,0 +1,473 @@
+<template>
+  <div v-if="authStore.isLoading" />
+  <div v-else-if="!authStore.user || authStore.user.role !== 'admin'">
+    <RouterLink to="/auth" /><!-- redirect handled in onMounted -->
+  </div>
+
+  <div v-else class="min-h-screen bg-background">
+    <Header />
+    <main class="container mx-auto px-4 pt-32 pb-20">
+
+      <!-- Header -->
+      <div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-12">
+        <div>
+          <span class="text-primary font-display text-sm tracking-widest uppercase mb-2 block">Management Center</span>
+          <h1 class="font-display text-4xl font-bold">Admin <span class="text-gradient">Dashboard</span></h1>
+        </div>
+        <div class="flex bg-card/40 backdrop-blur-md border border-border/50 rounded-2xl p-1 gap-1">
+          <button v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id" :class="[
+            'flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-bold transition-all',
+            activeTab === tab.id ? 'bg-primary text-primary-foreground shadow-glow' : 'text-muted-foreground hover:bg-white/5 hover:text-foreground'
+          ]">
+            <component :is="tab.icon" class="w-4 h-4" />{{ tab.label }}
+          </button>
+        </div>
+      </div>
+
+      <!-- Search & actions -->
+      <div class="flex flex-col sm:flex-row gap-4 mb-8">
+        <div class="relative flex-1">
+          <Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
+          <input type="text" v-model="searchQuery" :placeholder="`Search ${activeTab}...`"
+            class="w-full bg-card/50 border border-border/50 rounded-xl pl-10 pr-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all text-sm" />
+        </div>
+        <select v-if="activeTab === 'orders'" v-model="statusFilter"
+          class="bg-card/50 border border-border/50 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-primary/20 text-sm min-w-[140px]">
+          <option value="all">All Statuses</option>
+          <option v-for="s in Object.keys(STATUS_CONFIG)" :key="s" :value="s">{{ capitalize(s) }}</option>
+        </select>
+        <Button v-if="activeTab !== 'orders'" variant="hero" class="gap-2 sm:px-8" @click="showAddModal = true">
+          <Plus class="w-4 h-4" />Add New
+        </Button>
+      </div>
+
+      <!-- Loading -->
+      <div v-if="isLoading" class="flex items-center justify-center py-20">
+        <RefreshCw class="w-8 h-8 text-primary animate-spin" />
+      </div>
+
+      <!-- ORDERS -->
+      <div v-else-if="activeTab === 'orders'" class="grid gap-6">
+        <div v-for="order in filteredOrders" :key="order.id"
+          class="group relative bg-card/40 backdrop-blur-md border border-border/50 rounded-3xl overflow-hidden hover:border-primary/30 transition-all duration-300">
+          <div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-border/50">
+            <!-- Info -->
+            <div class="p-6 lg:w-1/4">
+              <div class="flex items-center justify-between mb-4">
+                <span class="text-xs font-bold text-muted-foreground border border-border/50 rounded-full px-2 py-0.5 uppercase tracking-tighter">Order #{{ order.id }}</span>
+                <span :class="`flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider ${statusColor(order.status)}`">
+                  <component :is="statusIcon(order.status)" class="w-3.5 h-3.5" />{{ order.status }}
+                </span>
+              </div>
+              <div class="space-y-1">
+                <h3 class="font-bold">{{ order.first_name }} {{ order.last_name }}</h3>
+                <p class="text-sm text-muted-foreground truncate">{{ order.email }}</p>
+              </div>
+              <div class="mt-4 pt-4 border-t border-border/50 text-xs text-muted-foreground">{{ new Date(order.created_at).toLocaleString() }}</div>
+            </div>
+            <!-- Details -->
+            <div class="p-6 lg:w-1/4 space-y-4">
+              <div class="flex justify-between items-start">
+                <div>
+                  <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1 block">Selected Material</span>
+                  <div class="flex items-center gap-2">
+                    <Layers class="w-3.5 h-3.5 text-primary" />
+                    <p class="text-sm font-bold uppercase">{{ order.material_name || "unknown" }}</p>
+                    <span class="text-[10px] text-muted-foreground">(@ {{ order.material_price || "0.00" }})</span>
+                  </div>
+                </div>
+                <div class="text-right">
+                  <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1 block">Quantity</span>
+                  <div class="flex items-center justify-end gap-1.5 px-2 py-1 bg-primary/10 rounded-lg text-primary font-bold">
+                    <Hash class="w-3 h-3" /><span class="text-sm">{{ order.quantity || 1 }}</span>
+                  </div>
+                </div>
+              </div>
+              <div>
+                <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1 block">Shipping Address</span>
+                <p class="text-xs text-muted-foreground line-clamp-2">{{ order.shipping_address }}</p>
+              </div>
+              <div v-if="order.notes" class="p-3 bg-background/50 border border-border/50 rounded-xl">
+                <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1 block">Project Notes</span>
+                <p class="text-[11px] text-muted-foreground italic">"{{ order.notes }}"</p>
+              </div>
+              <div class="flex items-center gap-2">
+                <ShieldCheck :class="`w-4 h-4 ${order.allow_portfolio ? 'text-emerald-500' : 'text-muted-foreground/30'}`" />
+                <span class="text-xs">{{ order.allow_portfolio ? "Portfolio Allowed" : "No Portfolio" }}</span>
+              </div>
+            </div>
+            <!-- Resources -->
+            <div class="p-6 lg:w-1/4 space-y-6">
+                <div class="flex items-center justify-between mb-3">
+                  <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">Source Files ({{ order.files?.length || 0 }})</span>
+                  <label class="p-1.5 bg-blue-500/10 text-blue-500 rounded-lg cursor-pointer hover:bg-blue-500 hover:text-white transition-all">
+                    <Plus class="w-3 h-3" />
+                    <input type="file" class="hidden" accept=".stl,.obj" @change="e => handleAttachFile(order.id, (e.target as HTMLInputElement).files?.[0])" />
+                  </label>
+                </div>
+                <!-- Model Link (if provided) -->
+                <div v-if="order.model_link" class="mb-4 p-3 bg-blue-500/5 border border-blue-500/20 rounded-xl">
+                  <span class="text-[10px] font-bold uppercase tracking-widest text-blue-500/60 mb-1 block">External Model Link</span>
+                  <div class="flex items-center justify-between gap-2 overflow-hidden">
+                    <p class="text-[10px] text-muted-foreground truncate">{{ order.model_link }}</p>
+                    <a :href="order.model_link" target="_blank" class="text-blue-500 hover:underline"><ExternalLink class="w-3.5 h-3.5" /></a>
+                  </div>
+                </div>
+                <div class="grid gap-3">
+                  <div v-for="(f, i) in order.files" :key="i" class="relative group/file bg-background/30 border border-border/50 rounded-2xl overflow-hidden hover:border-primary/30 transition-all flex h-20">
+                    <!-- Preview -->
+                    <div class="w-20 bg-muted/20 flex items-center justify-center border-r border-border/50 overflow-hidden">
+                       <img v-if="f.preview_path" :src="`http://localhost:8000/${f.preview_path}`" class="w-full h-full object-contain p-1" />
+                       <FileBox v-else class="w-6 h-6 text-muted-foreground/30" />
+                       <div class="absolute inset-0 bg-primary/20 opacity-0 group-hover/file:opacity-100 transition-opacity flex items-center justify-center">
+                         <a :href="`http://localhost:8000/${f.file_path}`" target="_blank" class="bg-card w-8 h-8 rounded-full flex items-center justify-center shadow-lg"><Download class="w-4 h-4 text-primary" /></a>
+                       </div>
+                    </div>
+                    <!-- Info -->
+                    <div class="flex-1 p-3 flex flex-col justify-center min-w-0">
+                      <p class="text-[11px] font-bold truncate mb-1 pr-4">{{ f.filename }}</p>
+                      <div class="flex flex-wrap gap-2 items-center">
+                        <span class="text-[9px] text-muted-foreground uppercase opacity-60">{{ (f.file_size / 1024 / 1024).toFixed(1) }} MB</span>
+                        <div v-if="f.print_time || f.filament_g" class="flex gap-2 border-l border-border/50 pl-2">
+                          <span v-if="f.print_time" class="text-[9px] font-bold text-primary/80">⏱️ {{ f.print_time }}</span>
+                          <span v-if="f.filament_g" class="text-[9px] font-bold text-primary/80">⚖️ {{ f.filament_g.toFixed(1) }}g</span>
+                        </div>
+                      </div>
+                    </div>
+                    <!-- Quantity Badge -->
+                    <div class="absolute top-2 right-2 px-1.5 py-0.5 bg-background/80 backdrop-blur-md rounded-md border border-border/50 text-[10px] font-bold">x{{ f.quantity || 1 }}</div>
+                  </div>
+                </div>
+              <div class="pt-4 border-t border-border/50">
+                <div class="flex items-center justify-between mb-3">
+                  <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">Photo Report ({{ order.photos?.length || 0 }})</span>
+                  <label class="p-1.5 bg-primary/10 text-primary rounded-lg cursor-pointer hover:bg-primary hover:text-primary-foreground transition-all">
+                    <Plus class="w-3 h-3" />
+                    <input type="file" class="hidden" accept="image/*" @change="e => handleUploadPhoto(order.id, (e.target as HTMLInputElement).files?.[0])" />
+                  </label>
+                </div>
+                <div class="flex flex-wrap gap-2">
+                  <div v-for="(p, i) in order.photos" :key="i" class="relative group/img overflow-hidden rounded-lg border border-border/50 w-12 h-12 bg-background/50">
+                    <img :src="`http://localhost:8000/${p.file_path}`" class="w-full h-full object-cover" />
+                    <button @click="handleTogglePhotoPublic(p.id, p.is_public, order.allow_portfolio)"
+                      :class="`absolute top-0 right-0 p-0.5 rounded-bl-md bg-black/60 z-10 transition-colors ${p.is_public ? 'text-blue-400 hover:text-blue-300' : 'text-gray-400 hover:text-white'}`">
+                      <Eye v-if="p.is_public" class="w-2.5 h-2.5" /><EyeOff v-else class="w-2.5 h-2.5" />
+                    </button>
+                    <a :href="`http://localhost:8000/${p.file_path}`" target="_blank" class="absolute inset-0 flex items-center justify-center bg-black/40 opacity-0 group-hover/img:opacity-100 transition-opacity">
+                      <ExternalLink class="w-4 h-4 text-white" />
+                    </a>
+                  </div>
+                  <div v-if="!order.photos?.length" class="w-full py-4 border border-dashed border-border/50 rounded-xl flex flex-col items-center justify-center opacity-40">
+                    <ImageIcon class="w-4 h-4 mb-1" /><span class="text-[10px]">No photos yet</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- Pricing & Actions -->
+            <div class="p-6 lg:w-1/4 bg-primary/5">
+              <div class="grid grid-cols-2 gap-2 mb-6">
+                <button v-for="s in Object.keys(STATUS_CONFIG)" :key="s"
+                  @click="handleUpdateStatus(order.id, s)"
+                  :class="`text-[9px] font-bold uppercase py-1.5 rounded-lg border transition-all ${order.status === s ? 'bg-primary text-primary-foreground border-primary' : 'bg-background hover:border-primary/30 border-border/50'}`">
+                  {{ s }}
+                </button>
+              </div>
+              <div class="pt-4 border-t border-border/50">
+                <div class="flex justify-between items-center mb-1">
+                  <span class="text-[10px] font-bold text-muted-foreground uppercase">Estimated</span>
+                  <span class="font-bold text-sm text-primary/80">{{ order.estimated_price }} EUR</span>
+                </div>
+                <div class="flex justify-between items-center">
+                  <span class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest">Final Price</span>
+                  <button @click="editingPrice = { id: order.id, price: order.total_price?.toString() ?? '' }" class="text-[10px] text-primary hover:underline font-bold">Edit</button>
+                </div>
+                <div class="text-2xl font-display font-bold">{{ order.total_price || "---" }} <span class="text-xs">EUR</span></div>
+                <button @click="toggleAdminChat(order.id)" class="mt-4 w-full flex items-center justify-center gap-2 py-2 rounded-xl border border-border/50 hover:border-primary/30 text-sm font-bold transition-all"
+                  :class="adminChatId === order.id ? 'bg-primary text-primary-foreground' : 'bg-background/50 text-muted-foreground hover:text-foreground'">
+                  <MessageCircle class="w-4 h-4" />{{ t("chat.open") }}
+                </button>
+              </div>
+            </div>
+          </div>
+          <!-- Admin Chat Panel -->
+          <div v-if="adminChatId === order.id" class="border-t border-border/50">
+            <OrderChat :orderId="order.id" @close="adminChatId = null" closable />
+          </div>
+        </div>
+      </div>
+
+      <!-- MATERIALS -->
+      <div v-else-if="activeTab === 'materials'" class="grid gap-4">
+        <div v-for="m in materials" :key="m.id"
+          class="p-6 bg-card/40 border border-border/50 rounded-2xl flex items-center justify-between group hover:border-primary/30 transition-all">
+          <div class="flex items-center gap-4">
+            <div :class="`p-3 rounded-xl bg-primary/10 text-primary ${!m.is_active && 'opacity-30 grayscale'}`"><Layers class="w-6 h-6" /></div>
+            <div>
+              <div class="flex items-center gap-2">
+                <h4 class="font-bold">{{ m.name_en }} / {{ m.name_ru }}</h4>
+              </div>
+              <p class="text-xs text-muted-foreground truncate max-w-md">{{ m.desc_en }}</p>
+            </div>
+          </div>
+          <div class="flex items-center gap-8">
+            <div class="text-right">
+              <p class="text-[10px] font-bold text-muted-foreground uppercase">Price / cm³</p>
+              <p class="font-display font-bold text-lg">{{ m.price_per_cm3 }} EUR</p>
+            </div>
+            <div class="flex items-center gap-2">
+              <button @click="editingMaterial = { ...m }" class="p-2 hover:bg-white/5 rounded-lg text-muted-foreground hover:text-primary transition-colors"><Edit2 class="w-4 h-4" /></button>
+              <button @click="toggleMaterialActive(m)" :class="`p-2 rounded-lg transition-colors ${m.is_active ? 'text-emerald-500 hover:bg-emerald-500/10' : 'text-rose-500 hover:bg-rose-500/10'}`">
+                <ToggleRight v-if="m.is_active" class="w-6 h-6" /><ToggleLeft v-else class="w-6 h-6" />
+              </button>
+              <button @click="handleDeleteMaterial(m.id, m.name_en)" class="p-2 hover:bg-rose-500/10 rounded-lg text-muted-foreground hover:text-rose-500 transition-colors"><Trash2 class="w-4 h-4" /></button>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- SERVICES -->
+      <div v-else-if="activeTab === 'services'" class="grid gap-4">
+        <div v-for="s in services" :key="s.id"
+          class="p-6 bg-card/40 border border-border/50 rounded-2xl flex items-center justify-between group hover:border-primary/30 transition-all">
+          <div class="flex items-center gap-4">
+            <div :class="`p-3 rounded-xl bg-blue-500/10 text-blue-500 ${!s.is_active && 'opacity-30 grayscale'}`"><Database class="w-6 h-6" /></div>
+            <div>
+              <div class="flex items-center gap-2">
+                <h4 class="font-bold">{{ t(s.name_key) }}</h4>
+                <span class="text-[10px] px-2 py-0.5 rounded-full bg-white/5 text-muted-foreground">{{ s.tech_type }}</span>
+              </div>
+              <p class="text-xs text-muted-foreground truncate max-w-md">{{ t(s.description_key) }}</p>
+            </div>
+          </div>
+          <div class="flex items-center gap-2">
+            <button @click="editingService = { ...s }" class="p-2 hover:bg-white/5 rounded-lg text-muted-foreground hover:text-primary transition-colors"><Edit2 class="w-4 h-4" /></button>
+            <button @click="toggleServiceActive(s)" :class="`p-2 rounded-lg transition-colors ${s.is_active ? 'text-emerald-500 hover:bg-emerald-500/10' : 'text-rose-500 hover:bg-rose-500/10'}`">
+              <ToggleRight v-if="s.is_active" class="w-6 h-6" /><ToggleLeft v-else class="w-6 h-6" />
+            </button>
+            <button @click="handleDeleteService(s.id, t(s.name_key))" class="p-2 hover:bg-rose-500/10 rounded-lg text-muted-foreground hover:text-rose-500 transition-colors"><Trash2 class="w-4 h-4" /></button>
+          </div>
+        </div>
+      </div>
+    </main>
+
+    <!-- ——— MODALS ——— -->
+    <Teleport to="body">
+      <!-- Price Modal -->
+      <Transition enter-active-class="transition duration-150" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
+        <div v-if="editingPrice" class="fixed inset-0 z-[100] flex items-center justify-center p-4">
+          <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" @click="editingPrice = null" />
+          <div class="relative w-full max-w-sm bg-card border border-border/50 rounded-3xl p-8 shadow-2xl">
+            <h3 class="text-xl font-bold font-display mb-6">Update Final Price</h3>
+            <div class="space-y-4">
+              <input v-model="editingPrice.price" type="number" step="0.01"
+                class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 font-bold" />
+              <div class="flex gap-3">
+                <Button variant="ghost" class="flex-1" @click="editingPrice = null">Cancel</Button>
+                <Button variant="hero" class="flex-1" @click="handleUpdatePrice">Save Price</Button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </Transition>
+
+      <!-- Material Modal -->
+      <Transition enter-active-class="transition duration-150" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
+        <div v-if="editingMaterial || (showAddModal && activeTab === 'materials')" class="fixed inset-0 z-[100] flex items-center justify-center p-4">
+          <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" @click="closeModals" />
+          <div class="relative w-full max-w-2xl bg-card border border-border/50 rounded-3xl p-8 shadow-2xl max-h-[90vh] overflow-y-auto">
+            <h3 class="text-xl font-bold font-display mb-6">{{ editingMaterial?.id ? "Edit Material" : "Add New Material" }}</h3>
+            <form @submit.prevent="handleSaveMaterial" class="space-y-6">
+              <!-- Names -->
+              <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Name (EN)</label><input v-model="matForm.name_en" required placeholder="PLA" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Name (RU)</label><input v-model="matForm.name_ru" required placeholder="ПЛА" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Name (ME)</label><input v-model="matForm.name_me" required placeholder="PLA" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+              </div>
+              
+              <!-- Price & Status -->
+              <div class="grid grid-cols-2 gap-4">
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Price per cm³ (EUR)</label><input v-model.number="matForm.price_per_cm3" type="number" step="0.001" required placeholder="0.05" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+                <div class="flex items-center gap-2 pt-6"><input v-model="matForm.is_active" type="checkbox" id="mat_active" class="w-5 h-5 rounded border-border" /><label for="mat_active" class="text-sm font-bold">Active and Visible</label></div>
+              </div>
+
+              <!-- Descriptions -->
+              <div class="space-y-4">
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Description (EN)</label><textarea v-model="matForm.desc_en" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 min-h-[60px]" /></div>
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Description (RU)</label><textarea v-model="matForm.desc_ru" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 min-h-[60px]" /></div>
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Description (ME)</label><textarea v-model="matForm.desc_me" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 min-h-[60px]" /></div>
+              </div>
+
+              <div class="flex gap-3 pt-4"><Button type="button" variant="ghost" class="flex-1" @click="closeModals">Cancel</Button><Button type="submit" variant="hero" class="flex-1">Save Changes</Button></div>
+            </form>
+          </div>
+        </div>
+      </Transition>
+
+      <!-- Service Modal -->
+      <Transition enter-active-class="transition duration-150" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
+        <div v-if="editingService || (showAddModal && activeTab === 'services')" class="fixed inset-0 z-[100] flex items-center justify-center p-4">
+          <div class="absolute inset-0 bg-background/80 backdrop-blur-sm" @click="closeModals" />
+          <div class="relative w-full max-w-lg bg-card border border-border/50 rounded-3xl p-8 shadow-2xl">
+            <h3 class="text-xl font-bold font-display mb-6">{{ editingService?.id ? "Edit Service" : "Add New Service" }}</h3>
+            <form @submit.prevent="handleSaveService" class="space-y-4">
+              <div class="grid grid-cols-2 gap-4">
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Name Key (i18n)</label><input v-model="svcForm.name_key" required placeholder="svc_fused_layer" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+                <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Tech Type</label><input v-model="svcForm.tech_type" placeholder="FDM" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3" /></div>
+              </div>
+              <div class="space-y-1"><label class="text-[10px] font-bold uppercase ml-1">Description Key</label><textarea v-model="svcForm.description_key" placeholder="svc_fdm_desc" class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 min-h-[120px] resize-y" /></div>
+              <div class="flex items-center gap-2 py-2"><input v-model="svcForm.is_active" type="checkbox" id="srv_active" class="w-5 h-5 rounded border-border" /><label for="srv_active" class="text-sm font-bold">Active and Visible</label></div>
+              <div class="flex gap-3 pt-4"><Button type="button" variant="ghost" class="flex-1" @click="closeModals">Cancel</Button><Button type="submit" variant="hero" class="flex-1">Save Changes</Button></div>
+            </form>
+          </div>
+        </div>
+      </Transition>
+    </Teleport>
+
+    <Footer />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, reactive, onMounted } from "vue";
+import { RouterLink, useRouter } from "vue-router";
+import { useI18n } from "vue-i18n";
+import { toast } from "vue-sonner";
+import { Package, Clock, CheckCircle2, Truck, XCircle, AlertCircle, FileText, ExternalLink, ShieldCheck, Eye, RefreshCw, Search, Filter, Layers, Settings, Plus, Edit2, Trash2, ToggleLeft, ToggleRight, Database, Image as ImageIcon, EyeOff, Hash, MessageCircle, FileBox, Download } from "lucide-vue-next";
+import Button from "@/components/ui/button.vue";
+import Header from "@/components/Header.vue";
+import Footer from "@/components/Footer.vue";
+import OrderChat from "@/components/OrderChat.vue";
+import { useAuthStore } from "@/stores/auth";
+import { adminGetOrders, adminUpdateOrder, adminGetMaterials, adminCreateMaterial, adminUpdateMaterial, adminDeleteMaterial, adminGetServices, adminCreateService, adminUpdateService, adminDeleteService, adminUploadOrderPhoto, adminUpdatePhotoStatus, adminAttachFile } from "@/lib/api";
+
+const { t } = useI18n();
+const router = useRouter();
+const authStore = useAuthStore();
+
+const STATUS_CONFIG: Record<string, { color: string; icon: any }> = {
+  pending:    { color: "text-amber-500 bg-amber-500/10",   icon: Clock },
+  processing: { color: "text-blue-500 bg-blue-500/10",    icon: RefreshCw },
+  shipped:    { color: "text-purple-500 bg-purple-500/10", icon: Truck },
+  completed:  { color: "text-emerald-500 bg-emerald-500/10", icon: CheckCircle2 },
+  cancelled:  { color: "text-rose-500 bg-rose-500/10",    icon: XCircle },
+};
+const statusColor = (s: string) => STATUS_CONFIG[s]?.color ?? "bg-muted text-muted-foreground";
+const statusIcon  = (s: string) => STATUS_CONFIG[s]?.icon  ?? AlertCircle;
+const capitalize  = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+
+const adminChatId = ref<number | null>(null);
+function toggleAdminChat(orderId: number) {
+  adminChatId.value = adminChatId.value === orderId ? null : orderId;
+}
+
+const tabs: { id: Tab; label: string; icon: any }[] = [
+  { id: "orders",    label: "Orders",    icon: Package },
+  { id: "materials", label: "Materials", icon: Layers },
+  { id: "services",  label: "Services",  icon: Database },
+];
+
+type Tab = "orders" | "materials" | "services";
+const activeTab    = ref<Tab>("orders");
+const orders       = ref<any[]>([]);
+const materials    = ref<any[]>([]);
+const services     = ref<any[]>([]);
+const isLoading    = ref(true);
+const searchQuery  = ref("");
+const statusFilter = ref("all");
+const editingPrice    = ref<{ id: number; price: string } | null>(null);
+const editingMaterial = ref<any | null>(null);
+const editingService  = ref<any | null>(null);
+const showAddModal    = ref(false);
+
+const matForm = reactive({ name_en: "", name_ru: "", name_me: "", desc_en: "", desc_ru: "", desc_me: "", price_per_cm3: 0, is_active: true });
+const svcForm = reactive({ name_key: "", description_key: "", tech_type: "", is_active: true });
+
+const filteredOrders = computed(() => orders.value.filter(o => {
+  const qs = searchQuery.value.toLowerCase();
+  const matchSearch = o.email?.toLowerCase().includes(qs) || o.first_name?.toLowerCase().includes(qs) || o.last_name?.toLowerCase().includes(qs) || String(o.id).includes(qs);
+  const matchStatus = statusFilter.value === "all" || o.status === statusFilter.value;
+  return matchSearch && matchStatus;
+}));
+
+async function fetchData() {
+  isLoading.value = true;
+  try {
+    if (activeTab.value === "orders")    orders.value    = await adminGetOrders();
+    else if (activeTab.value === "materials") materials.value = await adminGetMaterials();
+    else if (activeTab.value === "services")  services.value  = await adminGetServices();
+  } catch { toast.error(`Failed to load ${activeTab.value}`); }
+  finally { isLoading.value = false; }
+}
+
+watch(activeTab, fetchData, { immediate: false });
+onMounted(async () => {
+  if (!authStore.user || authStore.user.role !== "admin") { router.push("/auth"); return; }
+  fetchData();
+});
+
+async function handleUpdateStatus(id: number, status: string) {
+  try { await adminUpdateOrder(id, { status }); toast.success(`Status → ${status}`); fetchData(); }
+  catch { toast.error("Failed to update status"); }
+}
+async function handleUpdatePrice() {
+  if (!editingPrice.value) return;
+  const p = parseFloat(editingPrice.value.price);
+  if (isNaN(p)) { toast.error("Invalid price"); return; }
+  try { await adminUpdateOrder(editingPrice.value.id, { total_price: p }); toast.success("Price updated"); editingPrice.value = null; fetchData(); }
+  catch { toast.error("Failed to update price"); }
+}
+async function handleTogglePhotoPublic(photoId: number, current: boolean, allowed: boolean) {
+  if (!allowed && !current) { toast.error("User did not consent to portfolio"); return; }
+  try { await adminUpdatePhotoStatus(photoId, { is_public: !current }); toast.success(`Photo is now ${!current ? "Public" : "Private"}`); fetchData(); }
+  catch (e: any) { toast.error(e.message); }
+}
+async function handleUploadPhoto(orderId: number, file?: File) {
+  if (!file) return;
+  const order = orders.value.find(o => o.id === orderId);
+  const fd = new FormData(); fd.append("file", file); fd.append("is_public", order?.allow_portfolio ? "true" : "false");
+  try { await adminUploadOrderPhoto(orderId, fd); toast.success("Photo added"); fetchData(); }
+  catch (e: any) { toast.error(e.message); }
+}
+async function handleAttachFile(orderId: number, file?: File) {
+  if (!file) return;
+  const fd = new FormData(); fd.append("file", file);
+  try { await adminAttachFile(orderId, fd); toast.success("File attached and preview generated"); fetchData(); }
+  catch (e: any) { toast.error(e.message); }
+}
+async function handleDeleteMaterial(id: number, name: string) {
+  if (!window.confirm(`Delete material "${name}"?`)) return;
+  try { await adminDeleteMaterial(id); toast.success("Deleted"); fetchData(); }
+  catch { toast.error("Failed to delete"); }
+}
+async function handleDeleteService(id: number, name: string) {
+  if (!window.confirm(`Delete service "${name}"?`)) return;
+  try { await adminDeleteService(id); toast.success("Deleted"); fetchData(); }
+  catch { toast.error("Failed to delete"); }
+}
+async function toggleMaterialActive(m: any) { await adminUpdateMaterial(m.id, { is_active: !m.is_active }); fetchData(); }
+async function toggleServiceActive(s: any)  { await adminUpdateService(s.id,  { is_active: !s.is_active  }); fetchData(); }
+
+function openMaterialForm(m?: any) {
+  if (m) { Object.assign(matForm, m); editingMaterial.value = m; } 
+  else { Object.assign(matForm, { name_en: "", name_ru: "", name_me: "", desc_en: "", desc_ru: "", desc_me: "", price_per_cm3: 0, is_active: true }); showAddModal.value = true; }
+}
+async function handleSaveMaterial() {
+  try {
+    if (editingMaterial.value?.id) { await adminUpdateMaterial(editingMaterial.value.id, { ...matForm }); toast.success("Material updated"); }
+    else { await adminCreateMaterial({ ...matForm }); toast.success("Material created"); }
+    closeModals(); fetchData();
+  } catch { toast.error("Failed to save material"); }
+}
+async function handleSaveService() {
+  try {
+    if (editingService.value?.id) { await adminUpdateService(editingService.value.id, { ...svcForm }); toast.success("Service updated"); }
+    else { await adminCreateService({ ...svcForm }); toast.success("Service created"); }
+    closeModals(); fetchData();
+  } catch { toast.error("Failed to save service"); }
+}
+function closeModals() { editingMaterial.value = null; editingService.value = null; showAddModal.value = false; editingPrice.value = null; }
+
+// Watch editing to sync form data
+watch(editingMaterial, m => { if (m) Object.assign(matForm, m); });
+watch(editingService,  s => { if (s) Object.assign(svcForm, s); });
+</script>

+ 240 - 0
src/pages/Auth.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="min-h-screen relative flex items-center justify-center p-4 overflow-hidden bg-gradient-hero">
+    <div class="absolute inset-0 grid-pattern opacity-40" />
+    <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-primary/10 rounded-full blur-[120px]" />
+
+    <div
+      v-motion
+      :initial="{ opacity: 0, y: 20 }"
+      :enter="{ opacity: 1, y: 0 }"
+      class="w-full max-w-[440px] relative z-10"
+    >
+      <!-- Top bar -->
+      <div class="mb-8 flex items-center justify-between">
+        <RouterLink to="/" class="inline-flex items-center text-xs font-medium text-muted-foreground hover:text-primary transition-colors gap-2 px-1 py-1">
+          <ArrowLeft class="w-3.5 h-3.5" />{{ t("auth.back") }}
+        </RouterLink>
+        <LanguageSwitcher />
+      </div>
+
+      <div class="flex justify-center mb-10 text-center flex-col items-center">
+        <Logo />
+        <p class="text-[10px] uppercase tracking-[0.2em] text-muted-foreground mt-2 opacity-50">3D Printing Studio</p>
+      </div>
+
+      <div class="bg-card/40 backdrop-blur-xl border border-border/50 rounded-3xl p-8 shadow-card overflow-hidden">
+        <div class="text-center mb-8">
+          <h1 class="font-display text-2xl font-bold mb-2">{{ getTitle() }}</h1>
+          <Transition mode="out-in" enter-active-class="transition duration-150" enter-from-class="opacity-0 translate-y-1" enter-to-class="opacity-100 translate-y-0" leave-active-class="transition duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0 -translate-y-1">
+            <p :key="mode" class="text-muted-foreground text-sm">{{ getSubtitle() }}</p>
+          </Transition>
+        </div>
+
+        <form @submit.prevent="handleSubmit" class="space-y-5">
+          <!-- Email -->
+          <div v-if="mode !== 'reset'" class="space-y-2">
+            <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">{{ t("auth.fields.email") }}</label>
+            <div class="relative group">
+              <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
+                <Mail class="w-4 h-4" />
+              </div>
+              <input v-model="formData.email" type="email" required placeholder="john@example.com"
+                class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm" />
+            </div>
+          </div>
+
+          <!-- Reset Token -->
+          <div v-if="mode === 'reset'" class="space-y-2">
+            <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">{{ t("auth.reset.token") }}</label>
+            <div class="relative group">
+              <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
+                <KeyRound class="w-4 h-4" />
+              </div>
+              <input v-model="formData.token" type="text" required
+                class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm" />
+            </div>
+          </div>
+
+          <!-- Register extra fields -->
+          <div v-if="mode === 'register'" class="space-y-4">
+            <div class="grid grid-cols-2 gap-3">
+              <div class="space-y-1.5">
+                <label class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/60 ml-1">{{ t("upload.firstName") }}</label>
+                <input v-model="formData.firstName" type="text"
+                  class="w-full bg-background/50 border border-border/50 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-1 focus:ring-primary/30 text-sm" />
+              </div>
+              <div class="space-y-1.5">
+                <label class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/60 ml-1">{{ t("upload.lastName") }}</label>
+                <input v-model="formData.lastName" type="text"
+                  class="w-full bg-background/50 border border-border/50 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-1 focus:ring-primary/30 text-sm" />
+              </div>
+            </div>
+            <div class="space-y-1.5">
+              <label class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/60 ml-1">{{ t("upload.phone") }}</label>
+              <input v-model="formData.phone" type="tel"
+                class="w-full bg-background/50 border border-border/50 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-1 focus:ring-primary/30 text-sm" />
+            </div>
+            <div class="space-y-1.5">
+              <label class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/60 ml-1">{{ t("upload.shippingAddress") }}</label>
+              <textarea v-model="formData.address" rows="2"
+                class="w-full bg-background/50 border border-border/50 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-1 focus:ring-primary/30 text-sm resize-none" />
+            </div>
+          </div>
+
+          <!-- Password -->
+          <div v-if="mode !== 'forgot'" class="space-y-5">
+            <div class="space-y-2 pt-2">
+              <div class="flex justify-between items-center px-1">
+                <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
+                  {{ mode === "reset" ? "New Password" : t("auth.fields.password") }}
+                </label>
+                <button v-if="mode === 'login'" type="button" @click="mode = 'forgot'"
+                  class="text-[11px] text-primary hover:underline font-medium">{{ t("auth.forgot.link") }}</button>
+              </div>
+              <div class="relative group">
+                <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
+                  <Lock class="w-4 h-4" />
+                </div>
+                <input v-model="formData.password" type="password" required placeholder="••••••••"
+                  class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm" />
+              </div>
+            </div>
+
+            <div v-if="mode === 'register' || mode === 'reset'" class="space-y-2">
+              <label class="text-xs font-semibold uppercase tracking-wider text-muted-foreground ml-1">{{ t("auth.fields.confirmPassword") }}</label>
+              <div class="relative group">
+                <div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
+                  <ShieldCheck class="w-4 h-4" />
+                </div>
+                <input v-model="formData.confirmPassword" type="password" required placeholder="••••••••"
+                  class="w-full bg-background/50 border border-border/50 rounded-xl pl-11 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm" />
+              </div>
+            </div>
+          </div>
+
+          <Button type="submit" variant="hero" class="w-full mt-4" :disabled="isLoading">
+            <Loader2 v-if="isLoading" class="w-5 h-5 animate-spin" />
+            <template v-else>
+              <span>
+                {{ mode === "login" ? t("auth.login.submit") : "" }}
+                {{ mode === "register" ? t("auth.register.submit") : "" }}
+                {{ mode === "forgot" ? t("auth.forgot.submit") : "" }}
+                {{ mode === "reset" ? t("auth.reset.submit") : "" }}
+              </span>
+              <ArrowRight class="w-4 h-4 ml-2" />
+            </template>
+          </Button>
+
+          <!-- Social -->
+          <div v-if="mode === 'login' || mode === 'register'" class="space-y-4 pt-2">
+            <div class="relative">
+              <div class="absolute inset-0 flex items-center"><span class="w-full border-t border-border/50" /></div>
+              <div class="relative flex justify-center text-[10px] uppercase tracking-widest">
+                <span class="bg-card/40 px-2 text-muted-foreground backdrop-blur-md">{{ t("auth.orContinueWith") || "Or continue with" }}</span>
+              </div>
+            </div>
+            <div class="grid grid-cols-2 gap-3">
+              <button type="button" @click="toast.info('Google login coming soon')"
+                class="flex items-center justify-center gap-2 bg-background/50 hover:bg-background/80 border border-border/50 rounded-xl py-2.5 px-4 transition-all hover:scale-[1.02] active:scale-[0.98]">
+                <svg class="w-4 h-4" viewBox="0 0 24 24">
+                  <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
+                  <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
+                  <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
+                  <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
+                </svg>
+                <span class="text-xs font-medium">Google</span>
+              </button>
+              <button type="button" @click="toast.info('Facebook login coming soon')"
+                class="flex items-center justify-center gap-2 bg-background/50 hover:bg-background/80 border border-border/50 rounded-xl py-2.5 px-4 transition-all hover:scale-[1.02] active:scale-[0.98]">
+                <svg class="w-4 h-4 fill-[#1877F2]" viewBox="0 0 24 24">
+                  <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
+                </svg>
+                <span class="text-xs font-medium">Facebook</span>
+              </button>
+            </div>
+          </div>
+        </form>
+
+        <div class="mt-8 pt-6 border-t border-border/50 text-center">
+          <button @click="toggleMode" class="text-sm text-muted-foreground hover:text-primary transition-colors font-medium">
+            {{ mode === "login" ? t("auth.login.toggle") : "" }}
+            {{ mode === "register" ? t("auth.register.toggle") : "" }}
+            {{ (mode === "forgot" || mode === "reset") ? t("auth.forgot.toggle") : "" }}
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { useI18n } from "vue-i18n";
+import { toast } from "vue-sonner";
+import { Mail, Lock, ArrowRight, Loader2, ShieldCheck, KeyRound, ArrowLeft } from "lucide-vue-next";
+import Button from "@/components/ui/button.vue";
+import Logo from "@/components/Logo.vue";
+import LanguageSwitcher from "@/components/LanguageSwitcher.vue";
+import { loginUser, registerUser, forgotPassword, resetPassword } from "@/lib/api";
+import { useAuthStore } from "@/stores/auth";
+import { currentLanguage } from "@/i18n";
+
+type AuthMode = "login" | "register" | "forgot" | "reset";
+const { t } = useI18n();
+const router = useRouter();
+const route = useRoute();
+const authStore = useAuthStore();
+const mode = ref<AuthMode>("login");
+const isLoading = ref(false);
+const formData = reactive({ email: "", password: "", confirmPassword: "", firstName: "", lastName: "", phone: "", address: "", token: "" });
+
+onMounted(() => {
+  const token = route.query.token as string;
+  if (token) { mode.value = "reset"; formData.token = token; }
+});
+
+function getTitle() {
+  return { login: t("auth.login.title"), register: t("auth.register.title"), forgot: t("auth.forgot.title"), reset: t("auth.reset.title") }[mode.value];
+}
+function getSubtitle() {
+  return { login: t("auth.login.subtitle"), register: t("auth.register.subtitle"), forgot: t("auth.forgot.subtitle"), reset: t("auth.reset.subtitle") }[mode.value];
+}
+function toggleMode() {
+  if (mode.value === "login") mode.value = "register";
+  else if (mode.value === "register") mode.value = "login";
+  else mode.value = "login";
+}
+
+async function handleSubmit() {
+  if ((mode.value === "register" || mode.value === "reset") && formData.password !== formData.confirmPassword) {
+    toast.error("Passwords do not match"); return;
+  }
+  isLoading.value = true;
+  try {
+    if (mode.value === "login") {
+      const res = await loginUser({ email: formData.email, password: formData.password });
+      localStorage.setItem("token", res.access_token);
+      await authStore.refreshUser();
+      toast.success("Welcome back!");
+      router.push("/");
+    } else if (mode.value === "register") {
+      await registerUser({ email: formData.email, password: formData.password, first_name: formData.firstName, last_name: formData.lastName, phone: formData.phone, shipping_address: formData.address, preferred_language: currentLanguage() });
+      toast.success("Account created! Please log in.");
+      mode.value = "login";
+    } else if (mode.value === "forgot") {
+      const res = await forgotPassword(formData.email);
+      toast.success(res.message);
+      if (res.demo_token) { formData.token = res.demo_token; mode.value = "reset"; }
+    } else if (mode.value === "reset") {
+      await resetPassword({ token: formData.token, new_password: formData.password });
+      toast.success("Password changed! Please log in.");
+      mode.value = "login";
+    }
+  } catch (err: any) {
+    toast.error(err.message);
+  } finally {
+    isLoading.value = false;
+  }
+}
+</script>

+ 25 - 0
src/pages/Index.vue

@@ -0,0 +1,25 @@
+<template>
+  <div class="min-h-screen bg-background">
+    <Header />
+    <main>
+      <HeroSection />
+      <ServicesSection />
+      <ModelUploadSection />
+      <QuotingSection />
+      <ProcessSection />
+      <CTASection />
+    </main>
+    <Footer />
+  </div>
+</template>
+
+<script setup lang="ts">
+import Header from "@/components/Header.vue";
+import HeroSection from "@/components/HeroSection.vue";
+import ServicesSection from "@/components/ServicesSection.vue";
+import ModelUploadSection from "@/components/ModelUploadSection.vue";
+import QuotingSection from "@/components/QuotingSection.vue";
+import ProcessSection from "@/components/ProcessSection.vue";
+import CTASection from "@/components/CTASection.vue";
+import Footer from "@/components/Footer.vue";
+</script>

+ 15 - 0
src/pages/NotFound.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="flex min-h-screen items-center justify-center bg-muted">
+    <div class="text-center">
+      <h1 class="mb-4 text-4xl font-bold">404</h1>
+      <p class="mb-4 text-xl text-muted-foreground">Oops! Page not found</p>
+      <RouterLink to="/" class="text-primary underline hover:text-primary/90">Return to Home</RouterLink>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRoute } from "vue-router";
+const route = useRoute();
+console.error("404 Error: User attempted to access non-existent route:", route.path);
+</script>

+ 208 - 0
src/pages/Orders.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="min-h-screen bg-background flex flex-col">
+    <Header />
+    <main class="flex-grow pt-24 pb-20">
+      <div class="container mx-auto px-4 max-w-5xl">
+        <div class="mb-10">
+          <RouterLink to="/" class="inline-flex items-center text-sm text-muted-foreground hover:text-primary transition-colors mb-6 group">
+            <ArrowLeft class="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" />{{ t("auth.back") }}
+          </RouterLink>
+          <h1 class="font-display text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-foreground to-foreground/60">
+            {{ t("nav.myOrders") || "My Orders" }}
+          </h1>
+          <p class="text-muted-foreground mt-2">Track your 3D printing projects</p>
+        </div>
+
+        <div v-if="isLoading" class="flex flex-col items-center justify-center py-20 gap-4">
+          <Loader2 class="w-10 h-10 animate-spin text-primary" />
+          <p class="text-muted-foreground animate-pulse">Loading order history...</p>
+        </div>
+
+        <div v-else-if="orders.length === 0"
+          v-motion :initial="{ opacity: 0, y: 20 }" :enter="{ opacity: 1, y: 0 }"
+          class="bg-card/40 backdrop-blur-xl border border-border/50 rounded-3xl p-20 text-center">
+          <div class="w-20 h-20 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-6">
+            <Package class="w-10 h-10 text-primary opacity-60" />
+          </div>
+          <h2 class="text-2xl font-bold mb-2">No orders yet</h2>
+          <p class="text-muted-foreground mb-8 max-w-sm mx-auto">Once you start a 3D printing project, you'll be able to track its progress right here.</p>
+          <Button variant="hero" :as="RouterLink" to="/#upload">Start New Project</Button>
+        </div>
+
+        <div v-else class="space-y-4">
+          <div
+            v-for="(order, idx) in orders"
+            :key="order.id"
+            v-motion
+            :initial="{ opacity: 0, x: -20 }"
+            :enter="{ opacity: 1, x: 0, transition: { delay: idx * 50 } }"
+            class="bg-card/40 backdrop-blur-xl border border-border/50 rounded-2xl p-6 hover:border-primary/30 transition-all group overflow-hidden relative"
+          >
+            <div class="absolute top-0 right-0 w-32 h-32 bg-primary/5 rounded-full blur-3xl -mr-16 -mt-16 group-hover:bg-primary/10 transition-colors" />
+
+            <div class="flex flex-col md:flex-row md:items-center justify-between gap-6 relative z-10">
+              <div class="flex items-center gap-5">
+                <div class="w-12 h-12 bg-background/80 rounded-xl flex items-center justify-center border border-border/50">
+                  <Package class="w-6 h-6 text-primary" />
+                </div>
+                <div>
+                  <h3 class="font-bold text-lg leading-none mb-1">Order #{{ order.id }}</h3>
+                  <p class="text-xs text-muted-foreground">{{ formatDate(order.created_at) }}</p>
+                </div>
+              </div>
+
+              <div class="flex flex-wrap items-center gap-4 md:gap-8">
+                <div class="flex flex-col">
+                  <span class="text-[10px] uppercase tracking-wider text-muted-foreground font-bold mb-1">Quantity</span>
+                  <div class="flex items-center gap-1.5 px-2.5 py-1 bg-primary/10 rounded-lg text-primary font-bold">
+                    <Hash class="w-3 h-3" /><span class="text-sm">{{ order.quantity || 1 }}</span>
+                  </div>
+                </div>
+                <div class="flex flex-col">
+                  <span class="text-[10px] uppercase tracking-wider text-muted-foreground font-bold mb-1">Status</span>
+                  <StatusBadge :status="order.status" />
+                </div>
+                <div class="flex flex-col">
+                  <span class="text-[10px] uppercase tracking-wider text-muted-foreground font-bold mb-1">Estimate</span>
+                  <span class="font-display font-bold text-lg">{{ order.total_price ? `${order.total_price} €` : "Pending..." }}</span>
+                </div>
+                <div class="flex items-center gap-2">
+                  <a v-if="order.model_link && !order.model_link.startsWith('javascript:')"
+                    :href="order.model_link" target="_blank" rel="noopener noreferrer"
+                    class="p-2 hover:bg-secondary rounded-lg transition-colors">
+                    <ExternalLink class="w-4 h-4" />
+                  </a>
+                  <RouterLink :to="`/order-success?id=${order.id}`">
+                    <Button variant="outline" size="sm">Details</Button>
+                  </RouterLink>
+                  <Button variant="ghost" size="sm" @click="toggleChat(order.id)" class="relative">
+                    <MessageCircle class="w-4 h-4" :class="openChatId === order.id ? 'text-primary' : ''" />
+                    <span class="ml-1 text-xs">{{ t("chat.open") }}</span>
+                  </Button>
+                </div>
+              </div>
+            </div>
+
+            <div v-if="order.notes" class="mt-8 p-4 bg-primary/5 border border-primary/10 rounded-2xl relative z-10">
+              <div class="flex items-center gap-2 mb-2">
+                <FileText class="w-3.5 h-3.5 text-primary" />
+                <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">My Notes</span>
+              </div>
+            <p class="text-xs text-muted-foreground italic leading-relaxed">"{{ order.notes }}"</p>
+            </div>
+
+            <!-- Files -->
+            <div v-if="order.files && order.files.length > 0" class="mt-8 pt-6 border-t border-border/50 relative z-10">
+              <div class="flex items-center gap-2 mb-4">
+                <FileBox class="w-3.5 h-3.5 text-primary" />
+                <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">My Project Files</span>
+              </div>
+              <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
+                <div v-for="file in order.files" :key="file.id" 
+                  class="bg-background/40 border border-border/50 rounded-2xl overflow-hidden group/file hover:border-primary/30 transition-all flex flex-col h-full">
+                  <div class="aspect-square bg-muted/20 flex items-center justify-center p-2 relative overflow-hidden">
+                    <img v-if="file.preview_path" :src="`http://localhost:8000/${file.preview_path}`" class="w-full h-full object-contain" />
+                    <FileBox v-else class="w-8 h-8 text-muted-foreground/20" />
+                    <div class="absolute inset-0 bg-primary/20 opacity-0 group-hover/file:opacity-100 transition-opacity flex items-center justify-center">
+                       <a :href="`http://localhost:8000/${file.file_path}`" target="_blank" class="bg-card w-8 h-8 rounded-full flex items-center justify-center shadow-lg"><Download class="w-4 h-4 text-primary" /></a>
+                    </div>
+                  </div>
+                  <div class="p-3">
+                    <p class="text-[10px] font-bold truncate">{{ file.filename }}</p>
+                    <div class="flex items-center gap-2 mt-1">
+                      <span class="text-[8px] text-muted-foreground bg-white/5 px-1 py-0.5 rounded uppercase">{{ (file.file_size / 1024 / 1024).toFixed(1) }} MB</span>
+                      <span v-if="file.quantity > 1" class="text-[8px] font-bold text-primary">x{{ file.quantity }}</span>
+                    </div>
+                    <div v-if="file.print_time || file.filament_g" class="flex flex-col gap-0.5 mt-2 pt-2 border-t border-border/10">
+                      <span v-if="file.print_time" class="text-[8px] text-primary/80">⏱️ {{ file.print_time }}</span>
+                      <span v-if="file.filament_g" class="text-[8px] text-primary/80">⚖️ {{ file.filament_g.toFixed(1) }}g</span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div v-if="order.photos && order.photos.length > 0" class="mt-8 pt-6 border-t border-border/50 relative z-10">
+              <div class="flex items-center gap-2 mb-4">
+                <ImageIcon class="w-3.5 h-3.5 text-primary" />
+                <span class="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">Progress Report</span>
+              </div>
+              <div class="flex flex-wrap gap-3">
+                <div v-for="photo in order.photos" :key="photo.id"
+                  class="group/photo relative w-20 h-20 rounded-xl overflow-hidden border border-border/50 bg-background/50">
+                  <img :src="`http://localhost:8000/${photo.file_path}`" class="w-full h-full object-cover" alt="Print Progress" />
+                  <a :href="`http://localhost:8000/${photo.file_path}`" target="_blank"
+                    class="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover/photo:opacity-100 transition-opacity">
+                    <ExternalLink class="w-4 h-4 text-white" />
+                  </a>
+                </div>
+              </div>
+            </div>
+
+            <!-- Expandable Chat -->
+            <div v-if="openChatId === order.id" class="mt-6 pt-4 border-t border-border/50 relative z-10">
+              <OrderChat :orderId="order.id" compact closable @close="openChatId = null" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </main>
+    <Footer />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, defineComponent, h } from "vue";
+import { RouterLink, useRouter } from "vue-router";
+import { useI18n } from "vue-i18n";
+import { Package, Clock, ShieldCheck, Truck, XCircle, ArrowLeft, Loader2, ExternalLink, Hash, FileText, Image as ImageIcon, MessageCircle, FileBox, Download } from "lucide-vue-next";
+import Button from "@/components/ui/button.vue";
+import Header from "@/components/Header.vue";
+import Footer from "@/components/Footer.vue";
+import OrderChat from "@/components/OrderChat.vue";
+import { getMyOrders } from "@/lib/api";
+
+const { t } = useI18n();
+const router = useRouter();
+const orders = ref<any[]>([]);
+const isLoading = ref(true);
+const openChatId = ref<number | null>(null);
+
+function toggleChat(orderId: number) {
+  openChatId.value = openChatId.value === orderId ? null : orderId;
+}
+
+// StatusBadge component defined inline
+const StatusBadge = defineComponent({
+  props: { status: String },
+  setup(props) {
+    const styles: Record<string, string> = {
+      pending: "bg-amber-500/10 text-amber-500 border-amber-500/20",
+      processing: "bg-blue-500/10 text-blue-500 border-blue-500/20",
+      shipped: "bg-indigo-500/10 text-indigo-500 border-indigo-500/20",
+      completed: "bg-emerald-500/10 text-emerald-500 border-emerald-500/20",
+      cancelled: "bg-destructive/10 text-destructive border-destructive/20",
+    };
+    const icons: Record<string, any> = { pending: Clock, processing: ShieldCheck, shipped: Truck, completed: Package, cancelled: XCircle };
+    return () => {
+      const s = props.status ?? "pending";
+      const Icon = icons[s] || Clock;
+      return h("span", { class: `inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold border ${styles[s] ?? styles.pending}` }, [
+        h(Icon, { class: "w-3.5 h-3.5" }),
+        h("span", { class: "capitalize" }, s),
+      ]);
+    };
+  },
+});
+
+function formatDate(dt: string) {
+  return new Date(dt).toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
+}
+
+onMounted(async () => {
+  if (!localStorage.getItem("token")) { router.push("/auth"); return; }
+  try { orders.value = await getMyOrders(); }
+  catch (e) { console.error("Failed to fetch orders:", e); }
+  finally { isLoading.value = false; }
+});
+</script>

+ 96 - 0
src/pages/Portfolio.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="min-h-screen bg-background">
+    <Header />
+    <main class="container mx-auto px-4 pt-32 pb-24">
+      <div class="text-center mb-16">
+        <div v-motion :initial="{ opacity: 0, scale: 0.9 }" :enter="{ opacity: 1, scale: 1 }"
+          class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary mb-6">
+          <Sparkles class="w-4 h-4" />
+          <span class="text-xs font-display font-medium tracking-wider uppercase">{{ t("nav.portfolio") || "Showcase" }}</span>
+        </div>
+        <h1 class="font-display text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
+          {{ t("portfolio.title") || "Public" }} <span class="text-gradient">{{ t("portfolio.titleGradient") || "Portfolio" }}</span>
+        </h1>
+        <p class="text-muted-foreground max-w-2xl mx-auto text-lg leading-relaxed">
+          {{ t("portfolio.description") || "Explore our successful 3D printing projects." }}
+        </p>
+      </div>
+
+      <div v-if="isLoading" class="flex flex-col items-center justify-center py-24 gap-4 opacity-50">
+        <Loader2 class="w-10 h-10 animate-spin text-primary" />
+        <p class="text-sm font-medium animate-pulse">Loading gallery...</p>
+      </div>
+
+      <div v-else-if="items.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+        <div
+          v-for="(item, idx) in items"
+          :key="item.id"
+          v-motion
+          :initial="{ opacity: 0, y: 20 }"
+          :enter="{ opacity: 1, y: 0, transition: { delay: idx * 50 } }"
+          class="group relative aspect-square overflow-hidden rounded-3xl border border-border/50 bg-card/40 backdrop-blur-sm cursor-pointer hover:border-primary/50 transition-all duration-500"
+          @click="selectedImage = `http://localhost:8000/${item.file_path}`"
+        >
+          <img :src="`http://localhost:8000/${item.file_path}`" :alt="item.material_name"
+            class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
+          <div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-500 flex flex-col justify-end p-6">
+            <span class="text-[10px] font-bold uppercase tracking-widest text-primary mb-1">{{ item.material_name }}</span>
+            <div class="flex items-center justify-between">
+              <p class="text-white font-bold">Order #{{ item.order_id }}</p>
+              <ExternalLink class="w-4 h-4 text-white/70" />
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div v-else class="text-center py-32 bg-card/20 border border-dashed border-border/50 rounded-[40px]">
+        <Camera class="w-12 h-12 text-muted-foreground mb-4 mx-auto opacity-20" />
+        <h3 class="text-xl font-bold mb-2">Our gallery is growing</h3>
+        <p class="text-muted-foreground">Check back soon for more amazing prints!</p>
+      </div>
+    </main>
+
+    <!-- Lightbox -->
+    <Teleport to="body">
+      <Transition enter-active-class="transition duration-200" enter-from-class="opacity-0" enter-to-class="opacity-100"
+        leave-active-class="transition duration-150" leave-from-class="opacity-100" leave-to-class="opacity-0">
+        <div v-if="selectedImage"
+          class="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-background/95 backdrop-blur-md"
+          @click="selectedImage = null">
+          <div
+            v-motion :initial="{ scale: 0.9, opacity: 0 }" :enter="{ scale: 1, opacity: 1 }"
+            class="relative max-w-5xl w-full max-h-[90vh] overflow-hidden rounded-2xl shadow-glow"
+            @click.stop>
+            <img :src="selectedImage" class="w-full h-full object-contain" />
+            <button @click="selectedImage = null"
+              class="absolute top-4 right-4 w-10 h-10 bg-black/50 hover:bg-black/80 text-white rounded-full flex items-center justify-center transition-colors">
+              ✕
+            </button>
+          </div>
+        </div>
+      </Transition>
+    </Teleport>
+
+    <Footer />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { Sparkles, Loader2, Camera, ExternalLink } from "lucide-vue-next";
+import Header from "@/components/Header.vue";
+import Footer from "@/components/Footer.vue";
+import { getPortfolio } from "@/lib/api";
+
+const { t } = useI18n();
+const items = ref<any[]>([]);
+const isLoading = ref(true);
+const selectedImage = ref<string | null>(null);
+
+onMounted(async () => {
+  try { items.value = await getPortfolio(); }
+  catch (e) { console.error("Failed to load portfolio:", e); }
+  finally { isLoading.value = false; }
+});
+</script>

+ 21 - 0
src/router/index.ts

@@ -0,0 +1,21 @@
+import { createRouter, createWebHistory } from "vue-router";
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes: [
+    { path: "/",          component: () => import("@/pages/Index.vue") },
+    { path: "/auth",      component: () => import("@/pages/Auth.vue") },
+    { path: "/orders",    component: () => import("@/pages/Orders.vue") },
+    { path: "/portfolio", component: () => import("@/pages/Portfolio.vue") },
+    { path: "/admin",     component: () => import("@/pages/Admin.vue") },
+    { path: "/:pathMatch(.*)*", component: () => import("@/pages/NotFound.vue") },
+  ],
+  scrollBehavior(to) {
+    if (to.hash) {
+      return { el: to.hash, behavior: "smooth" };
+    }
+    return { top: 0 };
+  },
+});
+
+export default router;

+ 51 - 0
src/stores/auth.ts

@@ -0,0 +1,51 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import { getCurrentUser } from "@/lib/api";
+
+export const useAuthStore = defineStore("auth", () => {
+  const user = ref<any>(null);
+  const isLoading = ref(true);
+  const showCompleteProfile = ref(false);
+  let initialized = false;
+
+  async function refreshUser() {
+    try {
+      const userData = await getCurrentUser();
+      user.value = userData;
+      showCompleteProfile.value = !!(
+        userData && (!userData.phone || !userData.shipping_address)
+      );
+    } catch {
+      user.value = null;
+      showCompleteProfile.value = false;
+    } finally {
+      isLoading.value = false;
+    }
+  }
+
+  function init() {
+    if (!initialized) {
+      initialized = true;
+      refreshUser();
+    }
+  }
+
+  function setUser(u: any) {
+    user.value = u;
+  }
+
+  function onProfileComplete() {
+    showCompleteProfile.value = false;
+    refreshUser();
+  }
+
+  return {
+    user,
+    isLoading,
+    showCompleteProfile,
+    init,
+    setUser,
+    refreshUser,
+    onProfileComplete,
+  };
+});

+ 7723 - 0
src/tailwind.config.lov.json

@@ -0,0 +1,7723 @@
+{
+  "theme": {
+    "container": {
+      "center": true,
+      "padding": "2rem",
+      "screens": {
+        "2xl": "1400px"
+      }
+    },
+    "accentColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      },
+      "auto": "auto"
+    },
+    "animation": {
+      "none": "none",
+      "spin": "spin 1s linear infinite",
+      "ping": "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite",
+      "pulse": "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
+      "bounce": "bounce 1s infinite",
+      "accordion-down": "accordion-down 0.2s ease-out",
+      "accordion-up": "accordion-up 0.2s ease-out"
+    },
+    "aria": {
+      "busy": "busy=\"true\"",
+      "checked": "checked=\"true\"",
+      "disabled": "disabled=\"true\"",
+      "expanded": "expanded=\"true\"",
+      "hidden": "hidden=\"true\"",
+      "pressed": "pressed=\"true\"",
+      "readonly": "readonly=\"true\"",
+      "required": "required=\"true\"",
+      "selected": "selected=\"true\""
+    },
+    "aspectRatio": {
+      "auto": "auto",
+      "square": "1 / 1",
+      "video": "16 / 9"
+    },
+    "backdropBlur": {
+      "0": "0",
+      "none": "",
+      "sm": "4px",
+      "DEFAULT": "8px",
+      "md": "12px",
+      "lg": "16px",
+      "xl": "24px",
+      "2xl": "40px",
+      "3xl": "64px"
+    },
+    "backdropBrightness": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "90": ".9",
+      "95": ".95",
+      "100": "1",
+      "105": "1.05",
+      "110": "1.1",
+      "125": "1.25",
+      "150": "1.5",
+      "200": "2"
+    },
+    "backdropContrast": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "100": "1",
+      "125": "1.25",
+      "150": "1.5",
+      "200": "2"
+    },
+    "backdropGrayscale": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "backdropHueRotate": {
+      "0": "0deg",
+      "15": "15deg",
+      "30": "30deg",
+      "60": "60deg",
+      "90": "90deg",
+      "180": "180deg"
+    },
+    "backdropInvert": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "backdropOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "backdropSaturate": {
+      "0": "0",
+      "50": ".5",
+      "100": "1",
+      "150": "1.5",
+      "200": "2"
+    },
+    "backdropSepia": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "backgroundColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "backgroundImage": {
+      "none": "none",
+      "gradient-to-t": "linear-gradient(to top, var(--tw-gradient-stops))",
+      "gradient-to-tr": "linear-gradient(to top right, var(--tw-gradient-stops))",
+      "gradient-to-r": "linear-gradient(to right, var(--tw-gradient-stops))",
+      "gradient-to-br": "linear-gradient(to bottom right, var(--tw-gradient-stops))",
+      "gradient-to-b": "linear-gradient(to bottom, var(--tw-gradient-stops))",
+      "gradient-to-bl": "linear-gradient(to bottom left, var(--tw-gradient-stops))",
+      "gradient-to-l": "linear-gradient(to left, var(--tw-gradient-stops))",
+      "gradient-to-tl": "linear-gradient(to top left, var(--tw-gradient-stops))"
+    },
+    "backgroundOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "backgroundPosition": {
+      "bottom": "bottom",
+      "center": "center",
+      "left": "left",
+      "left-bottom": "left bottom",
+      "left-top": "left top",
+      "right": "right",
+      "right-bottom": "right bottom",
+      "right-top": "right top",
+      "top": "top"
+    },
+    "backgroundSize": {
+      "auto": "auto",
+      "cover": "cover",
+      "contain": "contain"
+    },
+    "blur": {
+      "0": "0",
+      "none": "",
+      "sm": "4px",
+      "DEFAULT": "8px",
+      "md": "12px",
+      "lg": "16px",
+      "xl": "24px",
+      "2xl": "40px",
+      "3xl": "64px"
+    },
+    "borderColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      },
+      "DEFAULT": "#e5e7eb"
+    },
+    "borderOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "borderRadius": {
+      "none": "0px",
+      "sm": "calc(var(--radius) - 4px)",
+      "DEFAULT": "0.25rem",
+      "md": "calc(var(--radius) - 2px)",
+      "lg": "var(--radius)",
+      "xl": "0.75rem",
+      "2xl": "1rem",
+      "3xl": "1.5rem",
+      "full": "9999px"
+    },
+    "borderSpacing": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "borderWidth": {
+      "0": "0px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px",
+      "DEFAULT": "1px"
+    },
+    "boxShadow": {
+      "sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
+      "DEFAULT": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
+      "md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
+      "lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
+      "xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
+      "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
+      "inner": "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",
+      "none": "none"
+    },
+    "boxShadowColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "brightness": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "90": ".9",
+      "95": ".95",
+      "100": "1",
+      "105": "1.05",
+      "110": "1.1",
+      "125": "1.25",
+      "150": "1.5",
+      "200": "2"
+    },
+    "caretColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "colors": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "columns": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "auto": "auto",
+      "3xs": "16rem",
+      "2xs": "18rem",
+      "xs": "20rem",
+      "sm": "24rem",
+      "md": "28rem",
+      "lg": "32rem",
+      "xl": "36rem",
+      "2xl": "42rem",
+      "3xl": "48rem",
+      "4xl": "56rem",
+      "5xl": "64rem",
+      "6xl": "72rem",
+      "7xl": "80rem"
+    },
+    "content": {
+      "none": "none"
+    },
+    "contrast": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "100": "1",
+      "125": "1.25",
+      "150": "1.5",
+      "200": "2"
+    },
+    "cursor": {
+      "auto": "auto",
+      "default": "default",
+      "pointer": "pointer",
+      "wait": "wait",
+      "text": "text",
+      "move": "move",
+      "help": "help",
+      "not-allowed": "not-allowed",
+      "none": "none",
+      "context-menu": "context-menu",
+      "progress": "progress",
+      "cell": "cell",
+      "crosshair": "crosshair",
+      "vertical-text": "vertical-text",
+      "alias": "alias",
+      "copy": "copy",
+      "no-drop": "no-drop",
+      "grab": "grab",
+      "grabbing": "grabbing",
+      "all-scroll": "all-scroll",
+      "col-resize": "col-resize",
+      "row-resize": "row-resize",
+      "n-resize": "n-resize",
+      "e-resize": "e-resize",
+      "s-resize": "s-resize",
+      "w-resize": "w-resize",
+      "ne-resize": "ne-resize",
+      "nw-resize": "nw-resize",
+      "se-resize": "se-resize",
+      "sw-resize": "sw-resize",
+      "ew-resize": "ew-resize",
+      "ns-resize": "ns-resize",
+      "nesw-resize": "nesw-resize",
+      "nwse-resize": "nwse-resize",
+      "zoom-in": "zoom-in",
+      "zoom-out": "zoom-out"
+    },
+    "divideColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      },
+      "DEFAULT": "#e5e7eb"
+    },
+    "divideOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "divideWidth": {
+      "0": "0px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px",
+      "DEFAULT": "1px"
+    },
+    "dropShadow": {
+      "sm": "0 1px 1px rgb(0 0 0 / 0.05)",
+      "DEFAULT": [
+        "0 1px 2px rgb(0 0 0 / 0.1)",
+        "0 1px 1px rgb(0 0 0 / 0.06)"
+      ],
+      "md": [
+        "0 4px 3px rgb(0 0 0 / 0.07)",
+        "0 2px 2px rgb(0 0 0 / 0.06)"
+      ],
+      "lg": [
+        "0 10px 8px rgb(0 0 0 / 0.04)",
+        "0 4px 3px rgb(0 0 0 / 0.1)"
+      ],
+      "xl": [
+        "0 20px 13px rgb(0 0 0 / 0.03)",
+        "0 8px 5px rgb(0 0 0 / 0.08)"
+      ],
+      "2xl": "0 25px 25px rgb(0 0 0 / 0.15)",
+      "none": "0 0 #0000"
+    },
+    "fill": {
+      "none": "none",
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "flex": {
+      "1": "1 1 0%",
+      "auto": "1 1 auto",
+      "initial": "0 1 auto",
+      "none": "none"
+    },
+    "flexBasis": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "1/5": "20%",
+      "2/5": "40%",
+      "3/5": "60%",
+      "4/5": "80%",
+      "1/6": "16.666667%",
+      "2/6": "33.333333%",
+      "3/6": "50%",
+      "4/6": "66.666667%",
+      "5/6": "83.333333%",
+      "1/12": "8.333333%",
+      "2/12": "16.666667%",
+      "3/12": "25%",
+      "4/12": "33.333333%",
+      "5/12": "41.666667%",
+      "6/12": "50%",
+      "7/12": "58.333333%",
+      "8/12": "66.666667%",
+      "9/12": "75%",
+      "10/12": "83.333333%",
+      "11/12": "91.666667%",
+      "full": "100%"
+    },
+    "flexGrow": {
+      "0": "0",
+      "DEFAULT": "1"
+    },
+    "flexShrink": {
+      "0": "0",
+      "DEFAULT": "1"
+    },
+    "fontFamily": {
+      "sans": [
+        "ui-sans-serif",
+        "system-ui",
+        "sans-serif",
+        "\"Apple Color Emoji\"",
+        "\"Segoe UI Emoji\"",
+        "\"Segoe UI Symbol\"",
+        "\"Noto Color Emoji\""
+      ],
+      "serif": [
+        "ui-serif",
+        "Georgia",
+        "Cambria",
+        "\"Times New Roman\"",
+        "Times",
+        "serif"
+      ],
+      "mono": [
+        "ui-monospace",
+        "SFMono-Regular",
+        "Menlo",
+        "Monaco",
+        "Consolas",
+        "\"Liberation Mono\"",
+        "\"Courier New\"",
+        "monospace"
+      ],
+      "display": [
+        "Orbitron",
+        "sans-serif"
+      ],
+      "body": [
+        "Inter",
+        "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"
+        }
+      ],
+      "4xl": [
+        "2.25rem",
+        {
+          "lineHeight": "2.5rem"
+        }
+      ],
+      "5xl": [
+        "3rem",
+        {
+          "lineHeight": "1"
+        }
+      ],
+      "6xl": [
+        "3.75rem",
+        {
+          "lineHeight": "1"
+        }
+      ],
+      "7xl": [
+        "4.5rem",
+        {
+          "lineHeight": "1"
+        }
+      ],
+      "8xl": [
+        "6rem",
+        {
+          "lineHeight": "1"
+        }
+      ],
+      "9xl": [
+        "8rem",
+        {
+          "lineHeight": "1"
+        }
+      ]
+    },
+    "fontWeight": {
+      "thin": "100",
+      "extralight": "200",
+      "light": "300",
+      "normal": "400",
+      "medium": "500",
+      "semibold": "600",
+      "bold": "700",
+      "extrabold": "800",
+      "black": "900"
+    },
+    "gap": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "gradientColorStops": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "gradientColorStopPositions": {
+      "0%": "0%",
+      "5%": "5%",
+      "10%": "10%",
+      "15%": "15%",
+      "20%": "20%",
+      "25%": "25%",
+      "30%": "30%",
+      "35%": "35%",
+      "40%": "40%",
+      "45%": "45%",
+      "50%": "50%",
+      "55%": "55%",
+      "60%": "60%",
+      "65%": "65%",
+      "70%": "70%",
+      "75%": "75%",
+      "80%": "80%",
+      "85%": "85%",
+      "90%": "90%",
+      "95%": "95%",
+      "100%": "100%"
+    },
+    "grayscale": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "gridAutoColumns": {
+      "auto": "auto",
+      "min": "min-content",
+      "max": "max-content",
+      "fr": "minmax(0, 1fr)"
+    },
+    "gridAutoRows": {
+      "auto": "auto",
+      "min": "min-content",
+      "max": "max-content",
+      "fr": "minmax(0, 1fr)"
+    },
+    "gridColumn": {
+      "auto": "auto",
+      "span-1": "span 1 / span 1",
+      "span-2": "span 2 / span 2",
+      "span-3": "span 3 / span 3",
+      "span-4": "span 4 / span 4",
+      "span-5": "span 5 / span 5",
+      "span-6": "span 6 / span 6",
+      "span-7": "span 7 / span 7",
+      "span-8": "span 8 / span 8",
+      "span-9": "span 9 / span 9",
+      "span-10": "span 10 / span 10",
+      "span-11": "span 11 / span 11",
+      "span-12": "span 12 / span 12",
+      "span-full": "1 / -1"
+    },
+    "gridColumnEnd": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "13": "13",
+      "auto": "auto"
+    },
+    "gridColumnStart": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "13": "13",
+      "auto": "auto"
+    },
+    "gridRow": {
+      "auto": "auto",
+      "span-1": "span 1 / span 1",
+      "span-2": "span 2 / span 2",
+      "span-3": "span 3 / span 3",
+      "span-4": "span 4 / span 4",
+      "span-5": "span 5 / span 5",
+      "span-6": "span 6 / span 6",
+      "span-7": "span 7 / span 7",
+      "span-8": "span 8 / span 8",
+      "span-9": "span 9 / span 9",
+      "span-10": "span 10 / span 10",
+      "span-11": "span 11 / span 11",
+      "span-12": "span 12 / span 12",
+      "span-full": "1 / -1"
+    },
+    "gridRowEnd": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "13": "13",
+      "auto": "auto"
+    },
+    "gridRowStart": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "13": "13",
+      "auto": "auto"
+    },
+    "gridTemplateColumns": {
+      "1": "repeat(1, minmax(0, 1fr))",
+      "2": "repeat(2, minmax(0, 1fr))",
+      "3": "repeat(3, minmax(0, 1fr))",
+      "4": "repeat(4, minmax(0, 1fr))",
+      "5": "repeat(5, minmax(0, 1fr))",
+      "6": "repeat(6, minmax(0, 1fr))",
+      "7": "repeat(7, minmax(0, 1fr))",
+      "8": "repeat(8, minmax(0, 1fr))",
+      "9": "repeat(9, minmax(0, 1fr))",
+      "10": "repeat(10, minmax(0, 1fr))",
+      "11": "repeat(11, minmax(0, 1fr))",
+      "12": "repeat(12, minmax(0, 1fr))",
+      "none": "none",
+      "subgrid": "subgrid"
+    },
+    "gridTemplateRows": {
+      "1": "repeat(1, minmax(0, 1fr))",
+      "2": "repeat(2, minmax(0, 1fr))",
+      "3": "repeat(3, minmax(0, 1fr))",
+      "4": "repeat(4, minmax(0, 1fr))",
+      "5": "repeat(5, minmax(0, 1fr))",
+      "6": "repeat(6, minmax(0, 1fr))",
+      "7": "repeat(7, minmax(0, 1fr))",
+      "8": "repeat(8, minmax(0, 1fr))",
+      "9": "repeat(9, minmax(0, 1fr))",
+      "10": "repeat(10, minmax(0, 1fr))",
+      "11": "repeat(11, minmax(0, 1fr))",
+      "12": "repeat(12, minmax(0, 1fr))",
+      "none": "none",
+      "subgrid": "subgrid"
+    },
+    "height": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "1/5": "20%",
+      "2/5": "40%",
+      "3/5": "60%",
+      "4/5": "80%",
+      "1/6": "16.666667%",
+      "2/6": "33.333333%",
+      "3/6": "50%",
+      "4/6": "66.666667%",
+      "5/6": "83.333333%",
+      "full": "100%",
+      "screen": "100vh",
+      "svh": "100svh",
+      "lvh": "100lvh",
+      "dvh": "100dvh",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "hueRotate": {
+      "0": "0deg",
+      "15": "15deg",
+      "30": "30deg",
+      "60": "60deg",
+      "90": "90deg",
+      "180": "180deg"
+    },
+    "inset": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "full": "100%"
+    },
+    "invert": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "keyframes": {
+      "spin": {
+        "to": {
+          "transform": "rotate(360deg)"
+        }
+      },
+      "ping": {
+        "75%, 100%": {
+          "transform": "scale(2)",
+          "opacity": "0"
+        }
+      },
+      "pulse": {
+        "50%": {
+          "opacity": ".5"
+        }
+      },
+      "bounce": {
+        "0%, 100%": {
+          "transform": "translateY(-25%)",
+          "animationTimingFunction": "cubic-bezier(0.8,0,1,1)"
+        },
+        "50%": {
+          "transform": "none",
+          "animationTimingFunction": "cubic-bezier(0,0,0.2,1)"
+        }
+      },
+      "enter": {
+        "from": {
+          "opacity": "var(--tw-enter-opacity, 1)",
+          "transform": "translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))"
+        }
+      },
+      "exit": {
+        "to": {
+          "opacity": "var(--tw-exit-opacity, 1)",
+          "transform": "translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))"
+        }
+      },
+      "accordion-down": {
+        "from": {
+          "height": "0"
+        },
+        "to": {
+          "height": "var(--radix-accordion-content-height)"
+        }
+      },
+      "accordion-up": {
+        "from": {
+          "height": "var(--radix-accordion-content-height)"
+        },
+        "to": {
+          "height": "0"
+        }
+      }
+    },
+    "letterSpacing": {
+      "tighter": "-0.05em",
+      "tight": "-0.025em",
+      "normal": "0em",
+      "wide": "0.025em",
+      "wider": "0.05em",
+      "widest": "0.1em"
+    },
+    "lineHeight": {
+      "3": ".75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "none": "1",
+      "tight": "1.25",
+      "snug": "1.375",
+      "normal": "1.5",
+      "relaxed": "1.625",
+      "loose": "2"
+    },
+    "listStyleType": {
+      "none": "none",
+      "disc": "disc",
+      "decimal": "decimal"
+    },
+    "listStyleImage": {
+      "none": "none"
+    },
+    "margin": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "lineClamp": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6"
+    },
+    "maxHeight": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "none": "none",
+      "full": "100%",
+      "screen": "100vh",
+      "svh": "100svh",
+      "lvh": "100lvh",
+      "dvh": "100dvh",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "maxWidth": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "none": "none",
+      "xs": "20rem",
+      "sm": "24rem",
+      "md": "28rem",
+      "lg": "32rem",
+      "xl": "36rem",
+      "2xl": "42rem",
+      "3xl": "48rem",
+      "4xl": "56rem",
+      "5xl": "64rem",
+      "6xl": "72rem",
+      "7xl": "80rem",
+      "full": "100%",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content",
+      "prose": "65ch",
+      "screen-sm": "640px",
+      "screen-md": "768px",
+      "screen-lg": "1024px",
+      "screen-xl": "1280px",
+      "screen-2xl": "1536px"
+    },
+    "minHeight": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "full": "100%",
+      "screen": "100vh",
+      "svh": "100svh",
+      "lvh": "100lvh",
+      "dvh": "100dvh",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "minWidth": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "full": "100%",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "objectPosition": {
+      "bottom": "bottom",
+      "center": "center",
+      "left": "left",
+      "left-bottom": "left bottom",
+      "left-top": "left top",
+      "right": "right",
+      "right-bottom": "right bottom",
+      "right-top": "right top",
+      "top": "top"
+    },
+    "opacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "order": {
+      "1": "1",
+      "2": "2",
+      "3": "3",
+      "4": "4",
+      "5": "5",
+      "6": "6",
+      "7": "7",
+      "8": "8",
+      "9": "9",
+      "10": "10",
+      "11": "11",
+      "12": "12",
+      "first": "-9999",
+      "last": "9999",
+      "none": "0"
+    },
+    "outlineColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "outlineOffset": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px"
+    },
+    "outlineWidth": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px"
+    },
+    "padding": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "placeholderColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "placeholderOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "ringColor": {
+      "DEFAULT": "#3b82f6",
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "ringOffsetColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "ringOffsetWidth": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px"
+    },
+    "ringOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1",
+      "DEFAULT": "0.5"
+    },
+    "ringWidth": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px",
+      "DEFAULT": "3px"
+    },
+    "rotate": {
+      "0": "0deg",
+      "1": "1deg",
+      "2": "2deg",
+      "3": "3deg",
+      "6": "6deg",
+      "12": "12deg",
+      "45": "45deg",
+      "90": "90deg",
+      "180": "180deg"
+    },
+    "saturate": {
+      "0": "0",
+      "50": ".5",
+      "100": "1",
+      "150": "1.5",
+      "200": "2"
+    },
+    "scale": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "90": ".9",
+      "95": ".95",
+      "100": "1",
+      "105": "1.05",
+      "110": "1.1",
+      "125": "1.25",
+      "150": "1.5"
+    },
+    "screens": {
+      "sm": "640px",
+      "md": "768px",
+      "lg": "1024px",
+      "xl": "1280px",
+      "2xl": "1536px"
+    },
+    "scrollMargin": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "scrollPadding": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "sepia": {
+      "0": "0",
+      "DEFAULT": "100%"
+    },
+    "skew": {
+      "0": "0deg",
+      "1": "1deg",
+      "2": "2deg",
+      "3": "3deg",
+      "6": "6deg",
+      "12": "12deg"
+    },
+    "space": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "spacing": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "stroke": {
+      "none": "none",
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "strokeWidth": {
+      "0": "0",
+      "1": "1",
+      "2": "2"
+    },
+    "supports": {},
+    "data": {},
+    "textColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "textDecorationColor": {
+      "inherit": "inherit",
+      "current": "currentColor",
+      "transparent": "transparent",
+      "black": "#000",
+      "white": "#fff",
+      "slate": {
+        "50": "#f8fafc",
+        "100": "#f1f5f9",
+        "200": "#e2e8f0",
+        "300": "#cbd5e1",
+        "400": "#94a3b8",
+        "500": "#64748b",
+        "600": "#475569",
+        "700": "#334155",
+        "800": "#1e293b",
+        "900": "#0f172a",
+        "950": "#020617"
+      },
+      "gray": {
+        "50": "#f9fafb",
+        "100": "#f3f4f6",
+        "200": "#e5e7eb",
+        "300": "#d1d5db",
+        "400": "#9ca3af",
+        "500": "#6b7280",
+        "600": "#4b5563",
+        "700": "#374151",
+        "800": "#1f2937",
+        "900": "#111827",
+        "950": "#030712"
+      },
+      "zinc": {
+        "50": "#fafafa",
+        "100": "#f4f4f5",
+        "200": "#e4e4e7",
+        "300": "#d4d4d8",
+        "400": "#a1a1aa",
+        "500": "#71717a",
+        "600": "#52525b",
+        "700": "#3f3f46",
+        "800": "#27272a",
+        "900": "#18181b",
+        "950": "#09090b"
+      },
+      "neutral": {
+        "50": "#fafafa",
+        "100": "#f5f5f5",
+        "200": "#e5e5e5",
+        "300": "#d4d4d4",
+        "400": "#a3a3a3",
+        "500": "#737373",
+        "600": "#525252",
+        "700": "#404040",
+        "800": "#262626",
+        "900": "#171717",
+        "950": "#0a0a0a"
+      },
+      "stone": {
+        "50": "#fafaf9",
+        "100": "#f5f5f4",
+        "200": "#e7e5e4",
+        "300": "#d6d3d1",
+        "400": "#a8a29e",
+        "500": "#78716c",
+        "600": "#57534e",
+        "700": "#44403c",
+        "800": "#292524",
+        "900": "#1c1917",
+        "950": "#0c0a09"
+      },
+      "red": {
+        "50": "#fef2f2",
+        "100": "#fee2e2",
+        "200": "#fecaca",
+        "300": "#fca5a5",
+        "400": "#f87171",
+        "500": "#ef4444",
+        "600": "#dc2626",
+        "700": "#b91c1c",
+        "800": "#991b1b",
+        "900": "#7f1d1d",
+        "950": "#450a0a"
+      },
+      "orange": {
+        "50": "#fff7ed",
+        "100": "#ffedd5",
+        "200": "#fed7aa",
+        "300": "#fdba74",
+        "400": "#fb923c",
+        "500": "#f97316",
+        "600": "#ea580c",
+        "700": "#c2410c",
+        "800": "#9a3412",
+        "900": "#7c2d12",
+        "950": "#431407"
+      },
+      "amber": {
+        "50": "#fffbeb",
+        "100": "#fef3c7",
+        "200": "#fde68a",
+        "300": "#fcd34d",
+        "400": "#fbbf24",
+        "500": "#f59e0b",
+        "600": "#d97706",
+        "700": "#b45309",
+        "800": "#92400e",
+        "900": "#78350f",
+        "950": "#451a03"
+      },
+      "yellow": {
+        "50": "#fefce8",
+        "100": "#fef9c3",
+        "200": "#fef08a",
+        "300": "#fde047",
+        "400": "#facc15",
+        "500": "#eab308",
+        "600": "#ca8a04",
+        "700": "#a16207",
+        "800": "#854d0e",
+        "900": "#713f12",
+        "950": "#422006"
+      },
+      "lime": {
+        "50": "#f7fee7",
+        "100": "#ecfccb",
+        "200": "#d9f99d",
+        "300": "#bef264",
+        "400": "#a3e635",
+        "500": "#84cc16",
+        "600": "#65a30d",
+        "700": "#4d7c0f",
+        "800": "#3f6212",
+        "900": "#365314",
+        "950": "#1a2e05"
+      },
+      "green": {
+        "50": "#f0fdf4",
+        "100": "#dcfce7",
+        "200": "#bbf7d0",
+        "300": "#86efac",
+        "400": "#4ade80",
+        "500": "#22c55e",
+        "600": "#16a34a",
+        "700": "#15803d",
+        "800": "#166534",
+        "900": "#14532d",
+        "950": "#052e16"
+      },
+      "emerald": {
+        "50": "#ecfdf5",
+        "100": "#d1fae5",
+        "200": "#a7f3d0",
+        "300": "#6ee7b7",
+        "400": "#34d399",
+        "500": "#10b981",
+        "600": "#059669",
+        "700": "#047857",
+        "800": "#065f46",
+        "900": "#064e3b",
+        "950": "#022c22"
+      },
+      "teal": {
+        "50": "#f0fdfa",
+        "100": "#ccfbf1",
+        "200": "#99f6e4",
+        "300": "#5eead4",
+        "400": "#2dd4bf",
+        "500": "#14b8a6",
+        "600": "#0d9488",
+        "700": "#0f766e",
+        "800": "#115e59",
+        "900": "#134e4a",
+        "950": "#042f2e"
+      },
+      "cyan": {
+        "50": "#ecfeff",
+        "100": "#cffafe",
+        "200": "#a5f3fc",
+        "300": "#67e8f9",
+        "400": "#22d3ee",
+        "500": "#06b6d4",
+        "600": "#0891b2",
+        "700": "#0e7490",
+        "800": "#155e75",
+        "900": "#164e63",
+        "950": "#083344"
+      },
+      "sky": {
+        "50": "#f0f9ff",
+        "100": "#e0f2fe",
+        "200": "#bae6fd",
+        "300": "#7dd3fc",
+        "400": "#38bdf8",
+        "500": "#0ea5e9",
+        "600": "#0284c7",
+        "700": "#0369a1",
+        "800": "#075985",
+        "900": "#0c4a6e",
+        "950": "#082f49"
+      },
+      "blue": {
+        "50": "#eff6ff",
+        "100": "#dbeafe",
+        "200": "#bfdbfe",
+        "300": "#93c5fd",
+        "400": "#60a5fa",
+        "500": "#3b82f6",
+        "600": "#2563eb",
+        "700": "#1d4ed8",
+        "800": "#1e40af",
+        "900": "#1e3a8a",
+        "950": "#172554"
+      },
+      "indigo": {
+        "50": "#eef2ff",
+        "100": "#e0e7ff",
+        "200": "#c7d2fe",
+        "300": "#a5b4fc",
+        "400": "#818cf8",
+        "500": "#6366f1",
+        "600": "#4f46e5",
+        "700": "#4338ca",
+        "800": "#3730a3",
+        "900": "#312e81",
+        "950": "#1e1b4b"
+      },
+      "violet": {
+        "50": "#f5f3ff",
+        "100": "#ede9fe",
+        "200": "#ddd6fe",
+        "300": "#c4b5fd",
+        "400": "#a78bfa",
+        "500": "#8b5cf6",
+        "600": "#7c3aed",
+        "700": "#6d28d9",
+        "800": "#5b21b6",
+        "900": "#4c1d95",
+        "950": "#2e1065"
+      },
+      "purple": {
+        "50": "#faf5ff",
+        "100": "#f3e8ff",
+        "200": "#e9d5ff",
+        "300": "#d8b4fe",
+        "400": "#c084fc",
+        "500": "#a855f7",
+        "600": "#9333ea",
+        "700": "#7e22ce",
+        "800": "#6b21a8",
+        "900": "#581c87",
+        "950": "#3b0764"
+      },
+      "fuchsia": {
+        "50": "#fdf4ff",
+        "100": "#fae8ff",
+        "200": "#f5d0fe",
+        "300": "#f0abfc",
+        "400": "#e879f9",
+        "500": "#d946ef",
+        "600": "#c026d3",
+        "700": "#a21caf",
+        "800": "#86198f",
+        "900": "#701a75",
+        "950": "#4a044e"
+      },
+      "pink": {
+        "50": "#fdf2f8",
+        "100": "#fce7f3",
+        "200": "#fbcfe8",
+        "300": "#f9a8d4",
+        "400": "#f472b6",
+        "500": "#ec4899",
+        "600": "#db2777",
+        "700": "#be185d",
+        "800": "#9d174d",
+        "900": "#831843",
+        "950": "#500724"
+      },
+      "rose": {
+        "50": "#fff1f2",
+        "100": "#ffe4e6",
+        "200": "#fecdd3",
+        "300": "#fda4af",
+        "400": "#fb7185",
+        "500": "#f43f5e",
+        "600": "#e11d48",
+        "700": "#be123c",
+        "800": "#9f1239",
+        "900": "#881337",
+        "950": "#4c0519"
+      },
+      "border": "hsl(var(--border))",
+      "input": "hsl(var(--input))",
+      "ring": "hsl(var(--ring))",
+      "background": "hsl(var(--background))",
+      "foreground": "hsl(var(--foreground))",
+      "primary": {
+        "DEFAULT": "hsl(var(--primary))",
+        "foreground": "hsl(var(--primary-foreground))"
+      },
+      "secondary": {
+        "DEFAULT": "hsl(var(--secondary))",
+        "foreground": "hsl(var(--secondary-foreground))"
+      },
+      "destructive": {
+        "DEFAULT": "hsl(var(--destructive))",
+        "foreground": "hsl(var(--destructive-foreground))"
+      },
+      "muted": {
+        "DEFAULT": "hsl(var(--muted))",
+        "foreground": "hsl(var(--muted-foreground))"
+      },
+      "accent": {
+        "DEFAULT": "hsl(var(--accent))",
+        "foreground": "hsl(var(--accent-foreground))"
+      },
+      "popover": {
+        "DEFAULT": "hsl(var(--popover))",
+        "foreground": "hsl(var(--popover-foreground))"
+      },
+      "card": {
+        "DEFAULT": "hsl(var(--card))",
+        "foreground": "hsl(var(--card-foreground))"
+      },
+      "sidebar": {
+        "DEFAULT": "hsl(var(--sidebar-background))",
+        "foreground": "hsl(var(--sidebar-foreground))",
+        "primary": "hsl(var(--sidebar-primary))",
+        "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+        "accent": "hsl(var(--sidebar-accent))",
+        "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+        "border": "hsl(var(--sidebar-border))",
+        "ring": "hsl(var(--sidebar-ring))"
+      }
+    },
+    "textDecorationThickness": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px",
+      "auto": "auto",
+      "from-font": "from-font"
+    },
+    "textIndent": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem"
+    },
+    "textOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1"
+    },
+    "textUnderlineOffset": {
+      "0": "0px",
+      "1": "1px",
+      "2": "2px",
+      "4": "4px",
+      "8": "8px",
+      "auto": "auto"
+    },
+    "transformOrigin": {
+      "center": "center",
+      "top": "top",
+      "top-right": "top right",
+      "right": "right",
+      "bottom-right": "bottom right",
+      "bottom": "bottom",
+      "bottom-left": "bottom left",
+      "left": "left",
+      "top-left": "top left"
+    },
+    "transitionDelay": {
+      "0": "0s",
+      "75": "75ms",
+      "100": "100ms",
+      "150": "150ms",
+      "200": "200ms",
+      "300": "300ms",
+      "500": "500ms",
+      "700": "700ms",
+      "1000": "1000ms"
+    },
+    "transitionDuration": {
+      "0": "0s",
+      "75": "75ms",
+      "100": "100ms",
+      "150": "150ms",
+      "200": "200ms",
+      "300": "300ms",
+      "500": "500ms",
+      "700": "700ms",
+      "1000": "1000ms",
+      "DEFAULT": "150ms"
+    },
+    "transitionProperty": {
+      "none": "none",
+      "all": "all",
+      "DEFAULT": "color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter",
+      "colors": "color, background-color, border-color, text-decoration-color, fill, stroke",
+      "opacity": "opacity",
+      "shadow": "box-shadow",
+      "transform": "transform"
+    },
+    "transitionTimingFunction": {
+      "DEFAULT": "cubic-bezier(0.4, 0, 0.2, 1)",
+      "linear": "linear",
+      "in": "cubic-bezier(0.4, 0, 1, 1)",
+      "out": "cubic-bezier(0, 0, 0.2, 1)",
+      "in-out": "cubic-bezier(0.4, 0, 0.2, 1)"
+    },
+    "translate": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "full": "100%"
+    },
+    "size": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "1/5": "20%",
+      "2/5": "40%",
+      "3/5": "60%",
+      "4/5": "80%",
+      "1/6": "16.666667%",
+      "2/6": "33.333333%",
+      "3/6": "50%",
+      "4/6": "66.666667%",
+      "5/6": "83.333333%",
+      "1/12": "8.333333%",
+      "2/12": "16.666667%",
+      "3/12": "25%",
+      "4/12": "33.333333%",
+      "5/12": "41.666667%",
+      "6/12": "50%",
+      "7/12": "58.333333%",
+      "8/12": "66.666667%",
+      "9/12": "75%",
+      "10/12": "83.333333%",
+      "11/12": "91.666667%",
+      "full": "100%",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "width": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "auto": "auto",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "1/5": "20%",
+      "2/5": "40%",
+      "3/5": "60%",
+      "4/5": "80%",
+      "1/6": "16.666667%",
+      "2/6": "33.333333%",
+      "3/6": "50%",
+      "4/6": "66.666667%",
+      "5/6": "83.333333%",
+      "1/12": "8.333333%",
+      "2/12": "16.666667%",
+      "3/12": "25%",
+      "4/12": "33.333333%",
+      "5/12": "41.666667%",
+      "6/12": "50%",
+      "7/12": "58.333333%",
+      "8/12": "66.666667%",
+      "9/12": "75%",
+      "10/12": "83.333333%",
+      "11/12": "91.666667%",
+      "full": "100%",
+      "screen": "100vw",
+      "svw": "100svw",
+      "lvw": "100lvw",
+      "dvw": "100dvw",
+      "min": "min-content",
+      "max": "max-content",
+      "fit": "fit-content"
+    },
+    "willChange": {
+      "auto": "auto",
+      "scroll": "scroll-position",
+      "contents": "contents",
+      "transform": "transform"
+    },
+    "zIndex": {
+      "0": "0",
+      "10": "10",
+      "20": "20",
+      "30": "30",
+      "40": "40",
+      "50": "50",
+      "auto": "auto"
+    },
+    "animationDelay": {
+      "0": "0s",
+      "75": "75ms",
+      "100": "100ms",
+      "150": "150ms",
+      "200": "200ms",
+      "300": "300ms",
+      "500": "500ms",
+      "700": "700ms",
+      "1000": "1000ms"
+    },
+    "animationDuration": {
+      "0": "0s",
+      "75": "75ms",
+      "100": "100ms",
+      "150": "150ms",
+      "200": "200ms",
+      "300": "300ms",
+      "500": "500ms",
+      "700": "700ms",
+      "1000": "1000ms",
+      "DEFAULT": "150ms"
+    },
+    "animationTimingFunction": {
+      "DEFAULT": "cubic-bezier(0.4, 0, 0.2, 1)",
+      "linear": "linear",
+      "in": "cubic-bezier(0.4, 0, 1, 1)",
+      "out": "cubic-bezier(0, 0, 0.2, 1)",
+      "in-out": "cubic-bezier(0.4, 0, 0.2, 1)"
+    },
+    "animationFillMode": {
+      "none": "none",
+      "forwards": "forwards",
+      "backwards": "backwards",
+      "both": "both"
+    },
+    "animationDirection": {
+      "normal": "normal",
+      "reverse": "reverse",
+      "alternate": "alternate",
+      "alternate-reverse": "alternate-reverse"
+    },
+    "animationOpacity": {
+      "0": "0",
+      "5": "0.05",
+      "10": "0.1",
+      "15": "0.15",
+      "20": "0.2",
+      "25": "0.25",
+      "30": "0.3",
+      "35": "0.35",
+      "40": "0.4",
+      "45": "0.45",
+      "50": "0.5",
+      "55": "0.55",
+      "60": "0.6",
+      "65": "0.65",
+      "70": "0.7",
+      "75": "0.75",
+      "80": "0.8",
+      "85": "0.85",
+      "90": "0.9",
+      "95": "0.95",
+      "100": "1",
+      "DEFAULT": 0
+    },
+    "animationTranslate": {
+      "0": "0px",
+      "1": "0.25rem",
+      "2": "0.5rem",
+      "3": "0.75rem",
+      "4": "1rem",
+      "5": "1.25rem",
+      "6": "1.5rem",
+      "7": "1.75rem",
+      "8": "2rem",
+      "9": "2.25rem",
+      "10": "2.5rem",
+      "11": "2.75rem",
+      "12": "3rem",
+      "14": "3.5rem",
+      "16": "4rem",
+      "20": "5rem",
+      "24": "6rem",
+      "28": "7rem",
+      "32": "8rem",
+      "36": "9rem",
+      "40": "10rem",
+      "44": "11rem",
+      "48": "12rem",
+      "52": "13rem",
+      "56": "14rem",
+      "60": "15rem",
+      "64": "16rem",
+      "72": "18rem",
+      "80": "20rem",
+      "96": "24rem",
+      "DEFAULT": "100%",
+      "px": "1px",
+      "0.5": "0.125rem",
+      "1.5": "0.375rem",
+      "2.5": "0.625rem",
+      "3.5": "0.875rem",
+      "1/2": "50%",
+      "1/3": "33.333333%",
+      "2/3": "66.666667%",
+      "1/4": "25%",
+      "2/4": "50%",
+      "3/4": "75%",
+      "full": "100%"
+    },
+    "animationScale": {
+      "0": "0",
+      "50": ".5",
+      "75": ".75",
+      "90": ".9",
+      "95": ".95",
+      "100": "1",
+      "105": "1.05",
+      "110": "1.1",
+      "125": "1.25",
+      "150": "1.5",
+      "DEFAULT": 0
+    },
+    "animationRotate": {
+      "0": "0deg",
+      "1": "1deg",
+      "2": "2deg",
+      "3": "3deg",
+      "6": "6deg",
+      "12": "12deg",
+      "45": "45deg",
+      "90": "90deg",
+      "180": "180deg",
+      "DEFAULT": "30deg"
+    },
+    "animationRepeat": {
+      "0": "0",
+      "1": "1",
+      "infinite": "infinite"
+    }
+  },
+  "corePlugins": [
+    "preflight",
+    "container",
+    "accessibility",
+    "pointerEvents",
+    "visibility",
+    "position",
+    "inset",
+    "isolation",
+    "zIndex",
+    "order",
+    "gridColumn",
+    "gridColumnStart",
+    "gridColumnEnd",
+    "gridRow",
+    "gridRowStart",
+    "gridRowEnd",
+    "float",
+    "clear",
+    "margin",
+    "boxSizing",
+    "lineClamp",
+    "display",
+    "aspectRatio",
+    "size",
+    "height",
+    "maxHeight",
+    "minHeight",
+    "width",
+    "minWidth",
+    "maxWidth",
+    "flex",
+    "flexShrink",
+    "flexGrow",
+    "flexBasis",
+    "tableLayout",
+    "captionSide",
+    "borderCollapse",
+    "borderSpacing",
+    "transformOrigin",
+    "translate",
+    "rotate",
+    "skew",
+    "scale",
+    "transform",
+    "animation",
+    "cursor",
+    "touchAction",
+    "userSelect",
+    "resize",
+    "scrollSnapType",
+    "scrollSnapAlign",
+    "scrollSnapStop",
+    "scrollMargin",
+    "scrollPadding",
+    "listStylePosition",
+    "listStyleType",
+    "listStyleImage",
+    "appearance",
+    "columns",
+    "breakBefore",
+    "breakInside",
+    "breakAfter",
+    "gridAutoColumns",
+    "gridAutoFlow",
+    "gridAutoRows",
+    "gridTemplateColumns",
+    "gridTemplateRows",
+    "flexDirection",
+    "flexWrap",
+    "placeContent",
+    "placeItems",
+    "alignContent",
+    "alignItems",
+    "justifyContent",
+    "justifyItems",
+    "gap",
+    "space",
+    "divideWidth",
+    "divideStyle",
+    "divideColor",
+    "divideOpacity",
+    "placeSelf",
+    "alignSelf",
+    "justifySelf",
+    "overflow",
+    "overscrollBehavior",
+    "scrollBehavior",
+    "textOverflow",
+    "hyphens",
+    "whitespace",
+    "textWrap",
+    "wordBreak",
+    "borderRadius",
+    "borderWidth",
+    "borderStyle",
+    "borderColor",
+    "borderOpacity",
+    "backgroundColor",
+    "backgroundOpacity",
+    "backgroundImage",
+    "gradientColorStops",
+    "boxDecorationBreak",
+    "backgroundSize",
+    "backgroundAttachment",
+    "backgroundClip",
+    "backgroundPosition",
+    "backgroundRepeat",
+    "backgroundOrigin",
+    "fill",
+    "stroke",
+    "strokeWidth",
+    "objectFit",
+    "objectPosition",
+    "padding",
+    "textAlign",
+    "textIndent",
+    "verticalAlign",
+    "fontFamily",
+    "fontSize",
+    "fontWeight",
+    "textTransform",
+    "fontStyle",
+    "fontVariantNumeric",
+    "lineHeight",
+    "letterSpacing",
+    "textColor",
+    "textOpacity",
+    "textDecoration",
+    "textDecorationColor",
+    "textDecorationStyle",
+    "textDecorationThickness",
+    "textUnderlineOffset",
+    "fontSmoothing",
+    "placeholderColor",
+    "placeholderOpacity",
+    "caretColor",
+    "accentColor",
+    "opacity",
+    "backgroundBlendMode",
+    "mixBlendMode",
+    "boxShadow",
+    "boxShadowColor",
+    "outlineStyle",
+    "outlineWidth",
+    "outlineOffset",
+    "outlineColor",
+    "ringWidth",
+    "ringColor",
+    "ringOpacity",
+    "ringOffsetWidth",
+    "ringOffsetColor",
+    "blur",
+    "brightness",
+    "contrast",
+    "dropShadow",
+    "grayscale",
+    "hueRotate",
+    "invert",
+    "saturate",
+    "sepia",
+    "filter",
+    "backdropBlur",
+    "backdropBrightness",
+    "backdropContrast",
+    "backdropGrayscale",
+    "backdropHueRotate",
+    "backdropInvert",
+    "backdropOpacity",
+    "backdropSaturate",
+    "backdropSepia",
+    "backdropFilter",
+    "transitionProperty",
+    "transitionDelay",
+    "transitionDuration",
+    "transitionTimingFunction",
+    "willChange",
+    "contain",
+    "content",
+    "forcedColorAdjust"
+  ],
+  "plugins": [
+    {
+      "config": {
+        "theme": {
+          "extend": {
+            "animationFillMode": {
+              "none": "none",
+              "forwards": "forwards",
+              "backwards": "backwards",
+              "both": "both"
+            },
+            "animationDirection": {
+              "normal": "normal",
+              "reverse": "reverse",
+              "alternate": "alternate",
+              "alternate-reverse": "alternate-reverse"
+            },
+            "animationRepeat": {
+              "0": "0",
+              "1": "1",
+              "infinite": "infinite"
+            },
+            "keyframes": {
+              "enter": {
+                "from": {
+                  "opacity": "var(--tw-enter-opacity, 1)",
+                  "transform": "translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))"
+                }
+              },
+              "exit": {
+                "to": {
+                  "opacity": "var(--tw-exit-opacity, 1)",
+                  "transform": "translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+  "darkMode": [
+    "class"
+  ],
+  "content": {
+    "relative": false,
+    "files": [
+      "./pages/**/*.{ts,tsx}",
+      "./components/**/*.{ts,tsx}",
+      "./app/**/*.{ts,tsx}",
+      "./src/**/*.{ts,tsx}"
+    ],
+    "extract": {},
+    "transform": {}
+  },
+  "prefix": "",
+  "presets": [],
+  "important": false,
+  "separator": ":",
+  "safelist": [],
+  "blocklist": []
+}

+ 85 - 0
tailwind.config.ts

@@ -0,0 +1,85 @@
+import type { Config } from "tailwindcss";
+
+const config: Config = {
+  darkMode: ["class"],
+  content: [
+    "./pages/**/*.{ts,tsx,vue}",
+    "./components/**/*.{ts,tsx,vue}",
+    "./app/**/*.{ts,tsx,vue}",
+    "./src/**/*.{ts,tsx,vue}",
+    "./index.html"
+  ],
+  prefix: "",
+  theme: {
+    container: {
+      center: true,
+      padding: "2rem",
+      screens: {
+        "2xl": "1400px",
+      },
+    },
+    extend: {
+      colors: {
+        border: "hsl(var(--border))",
+        input: "hsl(var(--input))",
+        ring: "hsl(var(--ring))",
+        background: "hsl(var(--background))",
+        foreground: "hsl(var(--foreground))",
+        primary: {
+          DEFAULT: "hsl(var(--primary))",
+          foreground: "hsl(var(--primary-foreground))",
+        },
+        secondary: {
+          DEFAULT: "hsl(var(--secondary))",
+          foreground: "hsl(var(--secondary-foreground))",
+        },
+        destructive: {
+          DEFAULT: "hsl(var(--destructive))",
+          foreground: "hsl(var(--destructive-foreground))",
+        },
+        muted: {
+          DEFAULT: "hsl(var(--muted))",
+          foreground: "hsl(var(--muted-foreground))",
+        },
+        accent: {
+          DEFAULT: "hsl(var(--accent))",
+          foreground: "hsl(var(--accent-foreground))",
+        },
+        popover: {
+          DEFAULT: "hsl(var(--popover))",
+          foreground: "hsl(var(--popover-foreground))",
+        },
+        card: {
+          DEFAULT: "hsl(var(--card))",
+          foreground: "hsl(var(--card-foreground))",
+        },
+      },
+      borderRadius: {
+        lg: "var(--radius)",
+        md: "calc(var(--radius) - 2px)",
+        sm: "calc(var(--radius) - 4px)",
+      },
+      fontFamily: {
+        display: ["var(--font-display)"],
+        body: ["var(--font-body)"],
+      },
+      keyframes: {
+        "accordion-down": {
+          from: { height: "0" },
+          to: { height: "var(--radix-accordion-content-height)" },
+        },
+        "accordion-up": {
+          from: { height: "var(--radix-accordion-content-height)" },
+          to: { height: "0" },
+        },
+      },
+      animation: {
+        "accordion-down": "accordion-down 0.2s ease-out",
+        "accordion-up": "accordion-up 0.2s ease-out",
+      },
+    },
+  },
+  plugins: [require("tailwindcss-animate")],
+};
+
+export default config;

+ 30 - 0
test_auth.py

@@ -0,0 +1,30 @@
+import requests
+
+def test_registration():
+    url = "http://localhost:8000/auth/register"
+    data = {
+        "email": "test@example.com",
+        "password": "password123",
+        "first_name": "Test",
+        "last_name": "User"
+    }
+    response = requests.post(url, json=data)
+    print(f"Registration Status: {response.status_code}")
+    print(f"Registration Response: {response.json()}")
+
+def test_login():
+    url = "http://localhost:8000/auth/login"
+    data = {
+        "email": "test@example.com",
+        "password": "password123"
+    }
+    response = requests.post(url, json=data)
+    print(f"Login Status: {response.status_code}")
+    print(f"Login Response: {response.json()}")
+
+if __name__ == "__main__":
+    try:
+        test_registration()
+        test_login()
+    except Exception as e:
+        print(f"Error: {e}")

+ 17 - 0
tmp_update_orders_notes.py

@@ -0,0 +1,17 @@
+from backend.db import execute_commit
+
+def migrate():
+    print("Migrating orders table...")
+    try:
+        execute_commit("ALTER TABLE orders ADD COLUMN quantity INT DEFAULT 1")
+    except Exception as e:
+        print(f"Quantity column might already exist: {e}")
+        
+    try:
+        execute_commit("ALTER TABLE orders ADD COLUMN notes TEXT")
+    except Exception as e:
+        print(f"Notes column might already exist: {e}")
+    print("Migration complete!")
+
+if __name__ == "__main__":
+    migrate()

+ 29 - 0
tmp_update_orders_schema.py

@@ -0,0 +1,29 @@
+import mysql.connector
+from db import DB_CONFIG
+
+def update_schema():
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        
+        try:
+            cursor.execute("ALTER TABLE orders ADD COLUMN user_id INT NULL;")
+            cursor.execute("ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;")
+        except mysql.connector.Error as e:
+            if e.errno == 1060: # Column already exists
+                print("Column user_id already exists")
+            else:
+                print(f"Error adding user_id: {e}")
+                    
+        conn.commit()
+        print("Schema update for orders successful")
+        
+    except mysql.connector.Error as err:
+        print(f"Error: {err}")
+    finally:
+        if 'conn' in locals() and conn.is_connected():
+            cursor.close()
+            conn.close()
+
+if __name__ == "__main__":
+    update_schema()

+ 37 - 0
tmp_update_schema.py

@@ -0,0 +1,37 @@
+import mysql.connector
+from db import DB_CONFIG
+
+def update_schema():
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        
+        # Add columns if they don't exist
+        columns_to_add = [
+            ("first_name", "VARCHAR(100)"),
+            ("last_name", "VARCHAR(100)"),
+            ("phone", "VARCHAR(20)"),
+            ("shipping_address", "TEXT")
+        ]
+        
+        for col_name, col_type in columns_to_add:
+            try:
+                cursor.execute(f"ALTER TABLE users ADD COLUMN {col_name} {col_type};")
+            except mysql.connector.Error as e:
+                if e.errno == 1060: # Column already exists
+                    print(f"Column {col_name} already exists")
+                else:
+                    print(f"Error adding {col_name}: {e}")
+                    
+        conn.commit()
+        print("Schema updated successfully")
+        
+    except mysql.connector.Error as err:
+        print(f"Error: {err}")
+    finally:
+        if 'conn' in locals() and conn.is_connected():
+            cursor.close()
+            conn.close()
+
+if __name__ == "__main__":
+    update_schema()

+ 24 - 0
tsconfig.json

@@ -0,0 +1,24 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "preserve",
+    "strict": true,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noFallthroughCasesInSwitch": true,
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 10 - 0
tsconfig.node.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 12 - 0
vite.config.ts

@@ -0,0 +1,12 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import path from "path";
+
+export default defineConfig({
+  plugins: [vue()],
+  resolve: {
+    alias: {
+      "@": path.resolve(__dirname, "./src"),
+    },
+  },
+});