diff --git a/backend/app/api/routes/notifications.py b/backend/app/api/routes/notifications.py index 4b2576782..da470799d 100644 --- a/backend/app/api/routes/notifications.py +++ b/backend/app/api/routes/notifications.py @@ -2,14 +2,17 @@ import json import logging +from datetime import datetime, timedelta -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import select +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy import delete, desc, func, select from sqlalchemy.ext.asyncio import AsyncSession from backend.app.core.database import get_db -from backend.app.models.notification import NotificationProvider +from backend.app.models.notification import NotificationLog, NotificationProvider from backend.app.schemas.notification import ( + NotificationLogResponse, + NotificationLogStats, NotificationProviderCreate, NotificationProviderResponse, NotificationProviderUpdate, @@ -46,6 +49,9 @@ def _provider_to_dict(provider: NotificationProvider) -> dict: "quiet_hours_enabled": provider.quiet_hours_enabled, "quiet_hours_start": provider.quiet_hours_start, "quiet_hours_end": provider.quiet_hours_end, + # Daily digest + "daily_digest_enabled": provider.daily_digest_enabled, + "daily_digest_time": provider.daily_digest_time, # Printer filter "printer_id": provider.printer_id, # Status tracking @@ -58,6 +64,10 @@ def _provider_to_dict(provider: NotificationProvider) -> dict: } +# ============================================================================ +# Provider List/Create Routes (no path parameters) +# ============================================================================ + @router.get("/", response_model=list[NotificationProviderResponse]) async def list_notification_providers(db: AsyncSession = Depends(get_db)): """List all notification providers.""" @@ -106,6 +116,204 @@ async def create_notification_provider( return _provider_to_dict(provider) +# ============================================================================ +# Static Path Routes (must come BEFORE parameterized routes) +# ============================================================================ + +@router.post("/test-config", response_model=NotificationTestResponse) +async def test_notification_config( + test_request: NotificationTestRequest, +): + """Test notification configuration before saving.""" + success, message = await notification_service.send_test_notification( + test_request.provider_type.value, test_request.config + ) + + return NotificationTestResponse(success=success, message=message) + + +@router.post("/test-all") +async def test_all_notification_providers(db: AsyncSession = Depends(get_db)): + """Send a test notification to all enabled providers.""" + result = await db.execute( + select(NotificationProvider).where(NotificationProvider.enabled == True) + ) + providers = result.scalars().all() + + if not providers: + return {"tested": 0, "success": 0, "failed": 0, "results": []} + + results = [] + success_count = 0 + failed_count = 0 + + for provider in providers: + config = json.loads(provider.config) if isinstance(provider.config, str) else provider.config + success, message = await notification_service.send_test_notification( + provider.provider_type, config + ) + + # Update provider status + if success: + provider.last_success = datetime.utcnow() + success_count += 1 + else: + provider.last_error = message + provider.last_error_at = datetime.utcnow() + failed_count += 1 + + results.append({ + "provider_id": provider.id, + "provider_name": provider.name, + "provider_type": provider.provider_type, + "success": success, + "message": message, + }) + + await db.commit() + + return { + "tested": len(providers), + "success": success_count, + "failed": failed_count, + "results": results, + } + + +# ============================================================================ +# Notification Log Routes (must come BEFORE /{provider_id} routes) +# ============================================================================ + +@router.get("/logs", response_model=list[NotificationLogResponse]) +async def get_notification_logs( + limit: int = Query(default=100, ge=1, le=500), + offset: int = Query(default=0, ge=0), + provider_id: int | None = Query(default=None), + event_type: str | None = Query(default=None), + success: bool | None = Query(default=None), + days: int | None = Query(default=7, ge=1, le=90, description="Filter logs from the last N days"), + db: AsyncSession = Depends(get_db), +): + """Get notification logs with optional filters.""" + query = select(NotificationLog).order_by(desc(NotificationLog.created_at)) + + # Apply filters + if provider_id is not None: + query = query.where(NotificationLog.provider_id == provider_id) + if event_type is not None: + query = query.where(NotificationLog.event_type == event_type) + if success is not None: + query = query.where(NotificationLog.success == success) + if days is not None: + cutoff = datetime.utcnow() - timedelta(days=days) + query = query.where(NotificationLog.created_at >= cutoff) + + query = query.offset(offset).limit(limit) + + result = await db.execute(query) + logs = result.scalars().all() + + # Get provider info for each log + response = [] + providers_cache: dict[int, NotificationProvider | None] = {} + + for log in logs: + if log.provider_id not in providers_cache: + provider_result = await db.execute( + select(NotificationProvider).where(NotificationProvider.id == log.provider_id) + ) + providers_cache[log.provider_id] = provider_result.scalar_one_or_none() + + provider = providers_cache[log.provider_id] + response.append(NotificationLogResponse( + id=log.id, + provider_id=log.provider_id, + provider_name=provider.name if provider else None, + provider_type=provider.provider_type if provider else None, + event_type=log.event_type, + title=log.title, + message=log.message, + success=log.success, + error_message=log.error_message, + printer_id=log.printer_id, + printer_name=log.printer_name, + created_at=log.created_at, + )) + + return response + + +@router.get("/logs/stats", response_model=NotificationLogStats) +async def get_notification_log_stats( + days: int = Query(default=7, ge=1, le=90, description="Statistics for the last N days"), + db: AsyncSession = Depends(get_db), +): + """Get notification log statistics.""" + cutoff = datetime.utcnow() - timedelta(days=days) + + # Total counts + total_result = await db.execute( + select(func.count(NotificationLog.id)).where(NotificationLog.created_at >= cutoff) + ) + total = total_result.scalar() or 0 + + success_result = await db.execute( + select(func.count(NotificationLog.id)).where( + NotificationLog.created_at >= cutoff, + NotificationLog.success == True + ) + ) + success_count = success_result.scalar() or 0 + + # By event type + event_result = await db.execute( + select(NotificationLog.event_type, func.count(NotificationLog.id)) + .where(NotificationLog.created_at >= cutoff) + .group_by(NotificationLog.event_type) + ) + by_event_type = {row[0]: row[1] for row in event_result.fetchall()} + + # By provider (need to join to get name) + provider_result = await db.execute( + select(NotificationProvider.name, func.count(NotificationLog.id)) + .join(NotificationProvider, NotificationLog.provider_id == NotificationProvider.id) + .where(NotificationLog.created_at >= cutoff) + .group_by(NotificationProvider.name) + ) + by_provider = {row[0]: row[1] for row in provider_result.fetchall()} + + return NotificationLogStats( + total=total, + success_count=success_count, + failure_count=total - success_count, + by_event_type=by_event_type, + by_provider=by_provider, + ) + + +@router.delete("/logs") +async def clear_notification_logs( + older_than_days: int = Query(default=30, ge=1, description="Delete logs older than N days"), + db: AsyncSession = Depends(get_db), +): + """Clear old notification logs.""" + cutoff = datetime.utcnow() - timedelta(days=older_than_days) + + result = await db.execute( + delete(NotificationLog).where(NotificationLog.created_at < cutoff) + ) + await db.commit() + + deleted_count = result.rowcount + logger.info(f"Deleted {deleted_count} notification logs older than {older_than_days} days") + + return {"deleted": deleted_count, "message": f"Deleted {deleted_count} logs older than {older_than_days} days"} + + +# ============================================================================ +# Provider Instance Routes (parameterized - must come LAST) +# ============================================================================ + @router.get("/{provider_id}", response_model=NotificationProviderResponse) async def get_notification_provider( provider_id: int, @@ -201,25 +409,11 @@ async def test_notification_provider( # Update provider status if success: - from datetime import datetime provider.last_success = datetime.utcnow() else: - from datetime import datetime provider.last_error = message provider.last_error_at = datetime.utcnow() await db.commit() return NotificationTestResponse(success=success, message=message) - - -@router.post("/test-config", response_model=NotificationTestResponse) -async def test_notification_config( - test_request: NotificationTestRequest, -): - """Test notification configuration before saving.""" - success, message = await notification_service.send_test_notification( - test_request.provider_type.value, test_request.config - ) - - return NotificationTestResponse(success=success, message=message) diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index 27574585b..0aded5596 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -1,9 +1,15 @@ -from fastapi import APIRouter, Depends +import json +from datetime import datetime + +from fastapi import APIRouter, Depends, UploadFile, File +from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from backend.app.core.database import get_db from backend.app.models.settings import Settings +from backend.app.models.notification import NotificationProvider +from backend.app.models.smart_plug import SmartPlug from backend.app.schemas.settings import AppSettings, AppSettingsUpdate @@ -46,8 +52,13 @@ async def get_settings(db: AsyncSession = Depends(get_db)): # Parse the value based on the expected type if setting.key in ["auto_archive", "save_thumbnails", "capture_finish_photo", "spoolman_enabled", "check_updates"]: settings_dict[setting.key] = setting.value.lower() == "true" - elif setting.key in ["default_filament_cost", "energy_cost_per_kwh"]: + elif setting.key in ["default_filament_cost", "energy_cost_per_kwh", "ams_temp_good", "ams_temp_fair"]: settings_dict[setting.key] = float(setting.value) + elif setting.key in ["ams_humidity_good", "ams_humidity_fair"]: + settings_dict[setting.key] = int(setting.value) + elif setting.key == "default_printer_id": + # Handle nullable integer + settings_dict[setting.key] = int(setting.value) if setting.value and setting.value != "None" else None else: settings_dict[setting.key] = setting.value @@ -66,6 +77,8 @@ async def update_settings( # Convert value to string for storage if isinstance(value, bool): str_value = "true" if value else "false" + elif value is None: + str_value = "None" else: str_value = str(value) await set_setting(db, key, str_value) @@ -133,3 +146,142 @@ async def update_spoolman_settings( # Return updated settings return await get_spoolman_settings(db) + + +@router.get("/backup") +async def export_backup(db: AsyncSession = Depends(get_db)): + """Export all settings, notification providers, and smart plugs as JSON backup.""" + # Get all settings + result = await db.execute(select(Settings)) + db_settings = result.scalars().all() + settings_data = {s.key: s.value for s in db_settings} + + # Get notification providers + result = await db.execute(select(NotificationProvider)) + providers = result.scalars().all() + providers_data = [] + for p in providers: + providers_data.append({ + "name": p.name, + "provider_type": p.provider_type, + "enabled": p.enabled, + "config": json.loads(p.config) if isinstance(p.config, str) else p.config, + "on_print_start": p.on_print_start, + "on_print_complete": p.on_print_complete, + "on_print_failed": p.on_print_failed, + "on_print_stopped": p.on_print_stopped, + "on_print_progress": p.on_print_progress, + "on_printer_offline": p.on_printer_offline, + "on_printer_error": p.on_printer_error, + "on_filament_low": p.on_filament_low, + "on_maintenance_due": p.on_maintenance_due, + "quiet_hours_enabled": p.quiet_hours_enabled, + "quiet_hours_start": p.quiet_hours_start, + "quiet_hours_end": p.quiet_hours_end, + }) + + # Get smart plugs + result = await db.execute(select(SmartPlug)) + plugs = result.scalars().all() + plugs_data = [] + for plug in plugs: + plugs_data.append({ + "name": plug.name, + "ip_address": plug.ip_address, + "enabled": plug.enabled, + "auto_off_enabled": plug.auto_off_enabled, + "auto_off_delay_minutes": plug.auto_off_delay_minutes, + }) + + backup = { + "version": "1.0", + "exported_at": datetime.utcnow().isoformat(), + "settings": settings_data, + "notification_providers": providers_data, + "smart_plugs": plugs_data, + } + + return JSONResponse( + content=backup, + headers={ + "Content-Disposition": f"attachment; filename=bambutrack-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json" + } + ) + + +@router.post("/restore") +async def import_backup( + file: UploadFile = File(...), + db: AsyncSession = Depends(get_db), +): + """Restore settings, notification providers, and smart plugs from JSON backup.""" + try: + content = await file.read() + backup = json.loads(content.decode("utf-8")) + except Exception as e: + return {"success": False, "message": f"Invalid backup file: {str(e)}"} + + restored = {"settings": 0, "notification_providers": 0, "smart_plugs": 0} + + # Restore settings + if "settings" in backup: + for key, value in backup["settings"].items(): + await set_setting(db, key, value) + restored["settings"] += 1 + + # Restore notification providers (skip duplicates by name) + if "notification_providers" in backup: + for provider_data in backup["notification_providers"]: + # Check if provider with same name exists + result = await db.execute( + select(NotificationProvider).where(NotificationProvider.name == provider_data["name"]) + ) + existing = result.scalar_one_or_none() + if not existing: + provider = NotificationProvider( + name=provider_data["name"], + provider_type=provider_data["provider_type"], + enabled=provider_data.get("enabled", True), + config=json.dumps(provider_data.get("config", {})), + on_print_start=provider_data.get("on_print_start", False), + on_print_complete=provider_data.get("on_print_complete", True), + on_print_failed=provider_data.get("on_print_failed", True), + on_print_stopped=provider_data.get("on_print_stopped", True), + on_print_progress=provider_data.get("on_print_progress", False), + on_printer_offline=provider_data.get("on_printer_offline", False), + on_printer_error=provider_data.get("on_printer_error", False), + on_filament_low=provider_data.get("on_filament_low", False), + on_maintenance_due=provider_data.get("on_maintenance_due", False), + quiet_hours_enabled=provider_data.get("quiet_hours_enabled", False), + quiet_hours_start=provider_data.get("quiet_hours_start"), + quiet_hours_end=provider_data.get("quiet_hours_end"), + ) + db.add(provider) + restored["notification_providers"] += 1 + + # Restore smart plugs (skip duplicates by IP) + if "smart_plugs" in backup: + for plug_data in backup["smart_plugs"]: + # Check if plug with same IP exists + result = await db.execute( + select(SmartPlug).where(SmartPlug.ip_address == plug_data["ip_address"]) + ) + existing = result.scalar_one_or_none() + if not existing: + plug = SmartPlug( + name=plug_data["name"], + ip_address=plug_data["ip_address"], + enabled=plug_data.get("enabled", True), + auto_off_enabled=plug_data.get("auto_off_enabled", False), + auto_off_delay_minutes=plug_data.get("auto_off_delay_minutes", 5), + ) + db.add(plug) + restored["smart_plugs"] += 1 + + await db.commit() + + return { + "success": True, + "message": f"Restored {restored['settings']} settings, {restored['notification_providers']} notification providers, {restored['smart_plugs']} smart plugs", + "restored": restored, + } diff --git a/backend/app/api/routes/smart_plugs.py b/backend/app/api/routes/smart_plugs.py index 78b74fccc..5648f8a86 100644 --- a/backend/app/api/routes/smart_plugs.py +++ b/backend/app/api/routes/smart_plugs.py @@ -1,7 +1,7 @@ """API routes for smart plug management.""" import logging -from datetime import datetime +from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession @@ -21,6 +21,7 @@ from backend.app.schemas.smart_plug import ( ) from backend.app.services.tasmota import tasmota_service from backend.app.services.printer_manager import printer_manager +from backend.app.services.notification_service import notification_service logger = logging.getLogger(__name__) @@ -211,6 +212,9 @@ async def get_plug_status(plug_id: int, db: AsyncSession = Depends(get_db)): if energy: energy_data = SmartPlugEnergy(**energy) + # Check power alerts + await check_power_alerts(plug, energy.get("power"), db) + return SmartPlugStatus( state=status["state"], reachable=status["reachable"], @@ -219,6 +223,61 @@ async def get_plug_status(plug_id: int, db: AsyncSession = Depends(get_db)): ) +async def check_power_alerts(plug: SmartPlug, current_power: float | None, db: AsyncSession): + """Check if power crosses alert thresholds and send notifications.""" + if not plug.power_alert_enabled or current_power is None: + return + + # Cooldown: don't alert more than once per 5 minutes + cooldown_minutes = 5 + if plug.power_alert_last_triggered: + time_since_last = datetime.utcnow() - plug.power_alert_last_triggered + if time_since_last < timedelta(minutes=cooldown_minutes): + return + + alert_triggered = False + alert_type = None + threshold = None + + # Check high threshold + if plug.power_alert_high is not None and current_power > plug.power_alert_high: + alert_triggered = True + alert_type = "high" + threshold = plug.power_alert_high + + # Check low threshold + if plug.power_alert_low is not None and current_power < plug.power_alert_low: + alert_triggered = True + alert_type = "low" + threshold = plug.power_alert_low + + if alert_triggered: + plug.power_alert_last_triggered = datetime.utcnow() + await db.commit() + + # Send notification + title = f"Power Alert: {plug.name}" + if alert_type == "high": + message = f"Power consumption is {current_power:.1f}W, above threshold of {threshold:.1f}W" + else: + message = f"Power consumption is {current_power:.1f}W, below threshold of {threshold:.1f}W" + + logger.info(f"Power alert triggered for {plug.name}: {message}") + + # Use printer_error event type for power alerts (closest match) + await notification_service.send_notification( + event_type="printer_error", + title=title, + message=message, + printer_id=plug.printer_id, + printer_name=plug.name, + context={ + "error_type": f"Power {alert_type.title()}", + "error_detail": message, + }, + ) + + @router.post("/test-connection") async def test_connection(data: SmartPlugTestConnection): """Test connection to a Tasmota device.""" diff --git a/backend/app/core/database.py b/backend/app/core/database.py index c586bd0c4..b43e29690 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -131,6 +131,66 @@ async def run_migrations(conn): # Column already exists pass + # Migration: Add power alert columns to smart_plugs + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN power_alert_enabled BOOLEAN DEFAULT 0" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN power_alert_high REAL" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN power_alert_low REAL" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN power_alert_last_triggered DATETIME" + )) + except Exception: + pass + + # Migration: Add schedule columns to smart_plugs + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN schedule_enabled BOOLEAN DEFAULT 0" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN schedule_on_time VARCHAR(5)" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE smart_plugs ADD COLUMN schedule_off_time VARCHAR(5)" + )) + except Exception: + pass + + # Migration: Add daily digest columns to notification_providers + try: + await conn.execute(text( + "ALTER TABLE notification_providers ADD COLUMN daily_digest_enabled BOOLEAN DEFAULT 0" + )) + except Exception: + pass + try: + await conn.execute(text( + "ALTER TABLE notification_providers ADD COLUMN daily_digest_time VARCHAR(5)" + )) + except Exception: + pass + async def seed_notification_templates(): """Seed default notification templates if they don't exist.""" diff --git a/backend/app/main.py b/backend/app/main.py index f60b40f79..f2379b14b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -993,10 +993,18 @@ async def lifespan(app: FastAPI): # Start the print scheduler asyncio.create_task(print_scheduler.run()) + # Start the smart plug scheduler for time-based on/off + smart_plug_manager.start_scheduler() + + # Start the notification digest scheduler + notification_service.start_digest_scheduler() + yield # Shutdown print_scheduler.stop() + smart_plug_manager.stop_scheduler() + notification_service.stop_digest_scheduler() printer_manager.disconnect_all() await close_spoolman_client() diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 2c65a9f04..2dae4e7f2 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -6,6 +6,7 @@ from backend.app.models.smart_plug import SmartPlug from backend.app.models.maintenance import MaintenanceType, PrinterMaintenance, MaintenanceHistory from backend.app.models.kprofile_note import KProfileNote from backend.app.models.notification_template import NotificationTemplate +from backend.app.models.notification import NotificationLog __all__ = [ "Printer", @@ -18,4 +19,5 @@ __all__ = [ "MaintenanceHistory", "KProfileNote", "NotificationTemplate", + "NotificationLog", ] diff --git a/backend/app/models/notification.py b/backend/app/models/notification.py index 206dea520..df9f8875c 100644 --- a/backend/app/models/notification.py +++ b/backend/app/models/notification.py @@ -1,4 +1,4 @@ -"""Notification provider model for push notifications.""" +"""Notification provider and log models for push notifications.""" from datetime import datetime @@ -8,6 +8,44 @@ from sqlalchemy.orm import relationship from backend.app.core.database import Base +class NotificationDigestQueue(Base): + """Model for queuing notifications to be sent in daily digest.""" + + __tablename__ = "notification_digest_queue" + + id = Column(Integer, primary_key=True, index=True) + provider_id = Column(Integer, ForeignKey("notification_providers.id", ondelete="CASCADE"), nullable=False) + event_type = Column(String(50), nullable=False) # print_start, print_complete, etc. + title = Column(String(255), nullable=False) + message = Column(Text, nullable=False) + printer_id = Column(Integer, ForeignKey("printers.id", ondelete="SET NULL"), nullable=True) + printer_name = Column(String(100), nullable=True) + created_at = Column(DateTime, default=datetime.utcnow, index=True) + + # Relationships + provider = relationship("NotificationProvider", back_populates="digest_queue") + + +class NotificationLog(Base): + """Model for logging sent notifications.""" + + __tablename__ = "notification_logs" + + id = Column(Integer, primary_key=True, index=True) + provider_id = Column(Integer, ForeignKey("notification_providers.id", ondelete="CASCADE"), nullable=False) + event_type = Column(String(50), nullable=False) # print_start, print_complete, etc. + title = Column(String(255), nullable=False) + message = Column(Text, nullable=False) + success = Column(Boolean, default=True) + error_message = Column(Text, nullable=True) + printer_id = Column(Integer, ForeignKey("printers.id", ondelete="SET NULL"), nullable=True) + printer_name = Column(String(100), nullable=True) # Store name in case printer is deleted + created_at = Column(DateTime, default=datetime.utcnow, index=True) + + # Relationships + provider = relationship("NotificationProvider", back_populates="logs") + + class NotificationProvider(Base): """Model for notification providers (WhatsApp, ntfy, Pushover, etc.).""" @@ -39,6 +77,10 @@ class NotificationProvider(Base): quiet_hours_start = Column(String(5), nullable=True) # HH:MM format, e.g., "22:00" quiet_hours_end = Column(String(5), nullable=True) # HH:MM format, e.g., "07:00" + # Daily digest (batch notifications into a single daily summary) + daily_digest_enabled = Column(Boolean, default=False) + daily_digest_time = Column(String(5), nullable=True) # HH:MM format, e.g., "08:00" + # Optional: Link to specific printer (NULL = all printers) printer_id = Column(Integer, ForeignKey("printers.id", ondelete="SET NULL"), nullable=True) @@ -53,3 +95,5 @@ class NotificationProvider(Base): # Relationships printer = relationship("Printer", back_populates="notification_providers") + logs = relationship("NotificationLog", back_populates="provider", cascade="all, delete-orphan") + digest_queue = relationship("NotificationDigestQueue", back_populates="provider", cascade="all, delete-orphan") diff --git a/backend/app/models/smart_plug.py b/backend/app/models/smart_plug.py index d5d007b83..bbff9ba68 100644 --- a/backend/app/models/smart_plug.py +++ b/backend/app/models/smart_plug.py @@ -1,5 +1,5 @@ from datetime import datetime -from sqlalchemy import String, Boolean, Integer, DateTime, ForeignKey, func +from sqlalchemy import String, Boolean, Integer, Float, DateTime, ForeignKey, func from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.core.database import Base @@ -33,6 +33,17 @@ class SmartPlug(Base): username: Mapped[str | None] = mapped_column(String(50), nullable=True) password: Mapped[str | None] = mapped_column(String(100), nullable=True) + # Power alerts + power_alert_enabled: Mapped[bool] = mapped_column(Boolean, default=False) + power_alert_high: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power > this (watts) + power_alert_low: Mapped[float | None] = mapped_column(Float, nullable=True) # Alert when power < this (watts) + power_alert_last_triggered: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Cooldown tracking + + # Schedule (time-based on/off) + schedule_enabled: Mapped[bool] = mapped_column(Boolean, default=False) + schedule_on_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format + schedule_off_time: Mapped[str | None] = mapped_column(String(5), nullable=True) # "HH:MM" format + # Status tracking last_state: Mapped[str | None] = mapped_column(String(10), nullable=True) # "ON"/"OFF" last_checked: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) diff --git a/backend/app/schemas/notification.py b/backend/app/schemas/notification.py index d5ad7c808..938733a3c 100644 --- a/backend/app/schemas/notification.py +++ b/backend/app/schemas/notification.py @@ -43,10 +43,14 @@ class NotificationProviderBase(BaseModel): quiet_hours_start: str | None = Field(default=None, description="Start time in HH:MM format") quiet_hours_end: str | None = Field(default=None, description="End time in HH:MM format") + # Daily digest + daily_digest_enabled: bool = Field(default=False, description="Batch notifications into daily digest") + daily_digest_time: str | None = Field(default=None, description="Time to send digest in HH:MM format") + # Printer filter printer_id: int | None = Field(default=None, description="Specific printer ID or null for all") - @field_validator("quiet_hours_start", "quiet_hours_end") + @field_validator("quiet_hours_start", "quiet_hours_end", "daily_digest_time") @classmethod def validate_time_format(cls, v: str | None) -> str | None: if v is None: @@ -95,6 +99,10 @@ class NotificationProviderUpdate(BaseModel): quiet_hours_start: str | None = None quiet_hours_end: str | None = None + # Daily digest + daily_digest_enabled: bool | None = None + daily_digest_time: str | None = None + # Printer filter printer_id: int | None = None @@ -168,3 +176,34 @@ class EmailConfig(BaseModel): from_email: str = Field(..., description="From email address") to_email: str = Field(..., description="Recipient email address") use_tls: bool = Field(default=True, description="Use TLS encryption") + + +# Notification Log schemas +class NotificationLogResponse(BaseModel): + """Schema for notification log API responses.""" + + id: int + provider_id: int + provider_name: str | None = None + provider_type: str | None = None + event_type: str + title: str + message: str + success: bool + error_message: str | None = None + printer_id: int | None = None + printer_name: str | None = None + created_at: datetime + + class Config: + from_attributes = True + + +class NotificationLogStats(BaseModel): + """Statistics for notification logs.""" + + total: int + success_count: int + failure_count: int + by_event_type: dict[str, int] + by_provider: dict[str, int] diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py index efe8db96b..3a8dc8649 100644 --- a/backend/app/schemas/settings.py +++ b/backend/app/schemas/settings.py @@ -29,6 +29,13 @@ class AppSettings(BaseModel): ams_temp_good: float = Field(default=28.0, description="Temperature threshold for good (blue): <= this value") ams_temp_fair: float = Field(default=35.0, description="Temperature threshold for fair (orange): <= this value, > is red") + # Date/time display format + date_format: str = Field(default="system", description="Date format: system, us, eu, iso") + time_format: str = Field(default="system", description="Time format: system, 12h, 24h") + + # Default printer for operations + default_printer_id: int | None = Field(default=None, description="Default printer ID for uploads, reprints, etc.") + class AppSettingsUpdate(BaseModel): """Schema for updating settings (all fields optional).""" @@ -49,3 +56,6 @@ class AppSettingsUpdate(BaseModel): ams_humidity_fair: int | None = None ams_temp_good: float | None = None ams_temp_fair: float | None = None + date_format: str | None = None + time_format: str | None = None + default_printer_id: int | None = None diff --git a/backend/app/schemas/smart_plug.py b/backend/app/schemas/smart_plug.py index d1e5b6e0c..dc1396b2e 100644 --- a/backend/app/schemas/smart_plug.py +++ b/backend/app/schemas/smart_plug.py @@ -15,6 +15,14 @@ class SmartPlugBase(BaseModel): off_temp_threshold: int = Field(default=70, ge=30, le=150) username: str | None = None password: str | None = None + # Power alerts + power_alert_enabled: bool = False + power_alert_high: float | None = Field(default=None, ge=0, le=5000) # Alert when power > this (watts) + power_alert_low: float | None = Field(default=None, ge=0, le=5000) # Alert when power < this (watts) + # Schedule + schedule_enabled: bool = False + schedule_on_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") # HH:MM format + schedule_off_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") # HH:MM format class SmartPlugCreate(SmartPlugBase): @@ -33,6 +41,14 @@ class SmartPlugUpdate(BaseModel): off_temp_threshold: int | None = Field(default=None, ge=30, le=150) username: str | None = None password: str | None = None + # Power alerts + power_alert_enabled: bool | None = None + power_alert_high: float | None = Field(default=None, ge=0, le=5000) + power_alert_low: float | None = Field(default=None, ge=0, le=5000) + # Schedule + schedule_enabled: bool | None = None + schedule_on_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") + schedule_off_time: str | None = Field(default=None, pattern=r"^([01]\d|2[0-3]):[0-5]\d$") class SmartPlugResponse(SmartPlugBase): @@ -40,6 +56,7 @@ class SmartPlugResponse(SmartPlugBase): last_state: str | None = None last_checked: datetime | None = None auto_off_executed: bool = False # True when auto-off was triggered after print + power_alert_last_triggered: datetime | None = None created_at: datetime updated_at: datetime diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py index 16f4f322d..6d0cf2fd4 100644 --- a/backend/app/services/notification_service.py +++ b/backend/app/services/notification_service.py @@ -1,5 +1,6 @@ """Notification service for sending push notifications via various providers.""" +import asyncio import json import logging import re @@ -14,7 +15,7 @@ import httpx from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from backend.app.models.notification import NotificationProvider +from backend.app.models.notification import NotificationLog, NotificationProvider, NotificationDigestQueue from backend.app.models.notification_template import NotificationTemplate logger = logging.getLogger(__name__) @@ -26,6 +27,8 @@ class NotificationService: def __init__(self): self._http_client: httpx.AsyncClient | None = None self._template_cache: dict[str, NotificationTemplate] = {} + self._digest_scheduler_task: asyncio.Task | None = None + self._last_digest_check: str = "" # "HH:MM" to avoid duplicate checks async def _get_client(self) -> httpx.AsyncClient: """Get or create HTTP client.""" @@ -150,6 +153,10 @@ class NotificationService: return await self._send_telegram(config, f"*{title}*\n{message}") elif provider_type == "email": return await self._send_email(config, title, message) + elif provider_type == "discord": + return await self._send_discord(config, title, message) + elif provider_type == "webhook": + return await self._send_webhook(config, title, message) else: return False, f"Unknown provider type: {provider_type}" except Exception as e: @@ -308,6 +315,70 @@ class NotificationService: except Exception as e: return False, f"Email error: {str(e)}" + async def _send_discord(self, config: dict, title: str, message: str) -> tuple[bool, str]: + """Send notification via Discord webhook.""" + webhook_url = config.get("webhook_url", "").strip() + + if not webhook_url: + return False, "Webhook URL is required" + + if not webhook_url.startswith("https://discord.com/api/webhooks/"): + return False, "Invalid Discord webhook URL" + + # Discord embed format for nicer messages + data = { + "embeds": [{ + "title": title, + "description": message, + "color": 0x00AE42, # Bambu green + }] + } + + client = await self._get_client() + response = await client.post(webhook_url, json=data) + + if response.status_code in (200, 204): + return True, "Message sent successfully" + else: + return False, f"HTTP {response.status_code}: {response.text[:200]}" + + async def _send_webhook(self, config: dict, title: str, message: str) -> tuple[bool, str]: + """Send notification via generic webhook (POST JSON).""" + webhook_url = config.get("webhook_url", "").strip() + auth_header = config.get("auth_header", "").strip() + custom_field_title = config.get("field_title", "title").strip() or "title" + custom_field_message = config.get("field_message", "message").strip() or "message" + + if not webhook_url: + return False, "Webhook URL is required" + + # Build payload with custom field names + data = { + custom_field_title: title, + custom_field_message: message, + "timestamp": datetime.now().isoformat(), + "source": "BambuTrack", + } + + headers = {"Content-Type": "application/json"} + if auth_header: + # Support "Bearer token" or just "token" format + if " " in auth_header: + headers["Authorization"] = auth_header + else: + headers["Authorization"] = f"Bearer {auth_header}" + + client = await self._get_client() + try: + response = await client.post(webhook_url, json=data, headers=headers) + + if response.status_code in (200, 201, 202, 204): + return True, "Webhook delivered successfully" + else: + return False, f"HTTP {response.status_code}: {response.text[:200]}" + except Exception as e: + return False, f"Webhook error: {str(e)}" + async def _send_to_provider( self, provider: NotificationProvider, title: str, message: str ) -> tuple[bool, str]: @@ -330,6 +401,10 @@ class NotificationService: return await self._send_telegram(config, f"*{title}*\n{message}") elif provider.provider_type == "email": return await self._send_email(config, title, message) + elif provider.provider_type == "discord": + return await self._send_discord(config, title, message) + elif provider.provider_type == "webhook": + return await self._send_webhook(config, title, message) else: return False, f"Unknown provider type: {provider.provider_type}" except Exception as e: @@ -373,18 +448,75 @@ class NotificationService: result = await db.execute(query) return list(result.scalars().all()) + async def _log_notification( + self, + db: AsyncSession, + provider_id: int, + event_type: str, + title: str, + message: str, + success: bool, + error_message: str | None = None, + printer_id: int | None = None, + printer_name: str | None = None, + ): + """Create a log entry for a sent notification.""" + try: + log = NotificationLog( + provider_id=provider_id, + event_type=event_type, + title=title, + message=message, + success=success, + error_message=error_message, + printer_id=printer_id, + printer_name=printer_name, + ) + db.add(log) + await db.commit() + except Exception as e: + logger.warning(f"Failed to log notification: {e}") + # Don't fail the notification just because logging failed + async def _send_to_providers( self, providers: list[NotificationProvider], title: str, message: str, db: AsyncSession, + event_type: str = "unknown", + printer_id: int | None = None, + printer_name: str | None = None, ): - """Send notification to multiple providers.""" + """Send notification to multiple providers and log the results.""" for provider in providers: try: + # Check if provider wants digest mode + if provider.daily_digest_enabled and provider.daily_digest_time: + await self._queue_for_digest( + provider=provider, + event_type=event_type, + title=title, + message=message, + db=db, + printer_id=printer_id, + printer_name=printer_name, + ) + continue + success, error = await self._send_to_provider(provider, title, message) await self._update_provider_status(db, provider.id, success, error if not success else None) + await self._log_notification( + db=db, + provider_id=provider.id, + event_type=event_type, + title=title, + message=message, + success=success, + error_message=error if not success else None, + printer_id=printer_id, + printer_name=printer_name, + ) if success: logger.info(f"Sent notification via {provider.name}") else: @@ -392,6 +524,17 @@ class NotificationService: except Exception as e: logger.exception(f"Error sending notification via {provider.name}") await self._update_provider_status(db, provider.id, False, str(e)) + await self._log_notification( + db=db, + provider_id=provider.id, + event_type=event_type, + title=title, + message=message, + success=False, + error_message=str(e), + printer_id=printer_id, + printer_name=printer_name, + ) async def on_print_start( self, printer_id: int, printer_name: str, data: dict, db: AsyncSession @@ -415,7 +558,7 @@ class NotificationService: logger.info(f"Found {len(providers)} providers for print_start: {[p.name for p in providers]}") title, message = await self._build_message_from_template(db, "print_start", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "print_start", printer_id, printer_name) async def on_print_complete( self, @@ -469,7 +612,7 @@ class NotificationService: logger.info(f"Found {len(providers)} providers for {event_field}: {[p.name for p in providers]}") title, message = await self._build_message_from_template(db, event_type, variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, event_type, printer_id, printer_name) async def on_print_progress( self, @@ -493,7 +636,7 @@ class NotificationService: } title, message = await self._build_message_from_template(db, "print_progress", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "print_progress", printer_id, printer_name) async def on_printer_offline( self, printer_id: int, printer_name: str, db: AsyncSession @@ -506,7 +649,7 @@ class NotificationService: variables = {"printer": printer_name} title, message = await self._build_message_from_template(db, "printer_offline", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "printer_offline", printer_id, printer_name) async def on_printer_error( self, @@ -528,7 +671,7 @@ class NotificationService: } title, message = await self._build_message_from_template(db, "printer_error", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "printer_error", printer_id, printer_name) async def on_filament_low( self, @@ -552,7 +695,7 @@ class NotificationService: } title, message = await self._build_message_from_template(db, "filament_low", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "filament_low", printer_id, printer_name) async def on_maintenance_due( self, @@ -584,12 +727,163 @@ class NotificationService: logger.info(f"Found {len(providers)} providers for maintenance_due: {[p.name for p in providers]}") title, message = await self._build_message_from_template(db, "maintenance_due", variables) - await self._send_to_providers(providers, title, message, db) + await self._send_to_providers(providers, title, message, db, "maintenance_due", printer_id, printer_name) def clear_template_cache(self): """Clear the template cache. Call this when templates are updated.""" self._template_cache.clear() + async def _queue_for_digest( + self, + provider: NotificationProvider, + event_type: str, + title: str, + message: str, + db: AsyncSession, + printer_id: int | None = None, + printer_name: str | None = None, + ): + """Queue a notification for later delivery in the daily digest.""" + try: + queue_entry = NotificationDigestQueue( + provider_id=provider.id, + event_type=event_type, + title=title, + message=message, + printer_id=printer_id, + printer_name=printer_name, + ) + db.add(queue_entry) + await db.commit() + logger.info(f"Queued notification for digest: {event_type} for provider {provider.name}") + except Exception as e: + logger.warning(f"Failed to queue notification for digest: {e}") + + async def send_digest(self, provider_id: int): + """Send all queued notifications as a single digest for a provider.""" + from backend.app.core.database import async_session + + async with async_session() as db: + # Get the provider + result = await db.execute( + select(NotificationProvider).where(NotificationProvider.id == provider_id) + ) + provider = result.scalar_one_or_none() + + if not provider or not provider.enabled: + return + + # Get all queued notifications for this provider + result = await db.execute( + select(NotificationDigestQueue) + .where(NotificationDigestQueue.provider_id == provider_id) + .order_by(NotificationDigestQueue.created_at) + ) + queue_entries = list(result.scalars().all()) + + if not queue_entries: + logger.debug(f"No queued notifications for provider {provider.name}") + return + + # Build digest message + title = f"Daily Digest - {len(queue_entries)} Events" + + # Group by event type + events_by_type: dict[str, list] = {} + for entry in queue_entries: + if entry.event_type not in events_by_type: + events_by_type[entry.event_type] = [] + events_by_type[entry.event_type].append(entry) + + # Format the digest body + body_parts = [] + for event_type, entries in events_by_type.items(): + event_label = event_type.replace("_", " ").title() + body_parts.append(f"== {event_label} ({len(entries)}) ==") + for entry in entries: + time_str = entry.created_at.strftime("%H:%M") + printer_info = f"[{entry.printer_name}] " if entry.printer_name else "" + body_parts.append(f" {time_str} {printer_info}{entry.title}") + body_parts.append("") + + body = "\n".join(body_parts) + + # Send the digest + success, error = await self._send_to_provider(provider, title, body) + + # Log the digest + await self._log_notification( + db=db, + provider_id=provider.id, + event_type="daily_digest", + title=title, + message=body, + success=success, + error_message=error if not success else None, + ) + + # Clear the queue + for entry in queue_entries: + await db.delete(entry) + await db.commit() + + if success: + logger.info(f"Sent daily digest with {len(queue_entries)} events to {provider.name}") + else: + logger.warning(f"Failed to send daily digest to {provider.name}: {error}") + + async def check_and_send_digests(self): + """Check all providers and send digests if it's their scheduled time.""" + from backend.app.core.database import async_session + + current_time = datetime.now().strftime("%H:%M") + + # Avoid duplicate checks within the same minute + if current_time == self._last_digest_check: + return + self._last_digest_check = current_time + + async with async_session() as db: + # Find all providers with digest enabled at this time + result = await db.execute( + select(NotificationProvider).where( + NotificationProvider.enabled == True, + NotificationProvider.daily_digest_enabled == True, + NotificationProvider.daily_digest_time == current_time, + ) + ) + providers = result.scalars().all() + + for provider in providers: + try: + await self.send_digest(provider.id) + except Exception as e: + logger.error(f"Error sending digest for provider {provider.id}: {e}") + + def start_digest_scheduler(self): + """Start the background scheduler for daily digest notifications.""" + if self._digest_scheduler_task is None: + self._digest_scheduler_task = asyncio.create_task(self._digest_scheduler_loop()) + logger.info("Notification digest scheduler started") + + def stop_digest_scheduler(self): + """Stop the background scheduler for daily digests.""" + if self._digest_scheduler_task: + self._digest_scheduler_task.cancel() + self._digest_scheduler_task = None + logger.info("Notification digest scheduler stopped") + + async def _digest_scheduler_loop(self): + """Background loop that checks for scheduled digests every minute.""" + while True: + try: + await self.check_and_send_digests() + except Exception as e: + logger.error(f"Error in digest scheduler: {e}") + + # Wait until the next minute + await asyncio.sleep(60) + # Global instance notification_service = NotificationService() diff --git a/backend/app/services/smart_plug_manager.py b/backend/app/services/smart_plug_manager.py index bb5cfba95..17b33b77d 100644 --- a/backend/app/services/smart_plug_manager.py +++ b/backend/app/services/smart_plug_manager.py @@ -23,11 +23,81 @@ class SmartPlugManager: def __init__(self): self._pending_off: dict[int, asyncio.Task] = {} # plug_id -> task self._loop: asyncio.AbstractEventLoop | None = None + self._scheduler_task: asyncio.Task | None = None + self._last_schedule_check: dict[int, str] = {} # plug_id -> "HH:MM" last executed def set_event_loop(self, loop: asyncio.AbstractEventLoop): """Set the event loop for async operations.""" self._loop = loop + def start_scheduler(self): + """Start the background scheduler for time-based plug control.""" + if self._scheduler_task is None: + self._scheduler_task = asyncio.create_task(self._schedule_loop()) + logger.info("Smart plug scheduler started") + + def stop_scheduler(self): + """Stop the background scheduler.""" + if self._scheduler_task: + self._scheduler_task.cancel() + self._scheduler_task = None + logger.info("Smart plug scheduler stopped") + + async def _schedule_loop(self): + """Background loop that checks scheduled on/off times every minute.""" + while True: + try: + await self._check_schedules() + except Exception as e: + logger.error(f"Error in schedule check: {e}") + + # Wait until the next minute + await asyncio.sleep(60) + + async def _check_schedules(self): + """Check all plugs for scheduled on/off times.""" + from backend.app.core.database import async_session + from backend.app.models.smart_plug import SmartPlug + + current_time = datetime.now().strftime("%H:%M") + + async with async_session() as db: + result = await db.execute( + select(SmartPlug).where( + SmartPlug.enabled == True, + SmartPlug.schedule_enabled == True, + ) + ) + plugs = result.scalars().all() + + for plug in plugs: + # Check if we should turn on + if plug.schedule_on_time == current_time: + last_check = self._last_schedule_check.get(plug.id) + if last_check != f"on:{current_time}": + logger.info(f"Schedule: Turning on plug '{plug.name}' at {current_time}") + success = await tasmota_service.turn_on(plug) + if success: + plug.last_state = "ON" + plug.last_checked = datetime.utcnow() + self._last_schedule_check[plug.id] = f"on:{current_time}" + + # Check if we should turn off + if plug.schedule_off_time == current_time: + last_check = self._last_schedule_check.get(plug.id) + if last_check != f"off:{current_time}": + logger.info(f"Schedule: Turning off plug '{plug.name}' at {current_time}") + success = await tasmota_service.turn_off(plug) + if success: + plug.last_state = "OFF" + plug.last_checked = datetime.utcnow() + self._last_schedule_check[plug.id] = f"off:{current_time}" + # Mark printer offline if linked + if plug.printer_id: + printer_manager.mark_printer_offline(plug.printer_id) + + await db.commit() + async def _get_plug_for_printer( self, printer_id: int, db: AsyncSession ) -> "SmartPlug | None": diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 353ed873c..9810b6537 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -252,6 +252,11 @@ export interface AppSettings { ams_humidity_fair: number; // <= this is orange, > is red ams_temp_good: number; // <= this is green/blue ams_temp_fair: number; // <= this is orange, > is red + // Date/time format settings + date_format: 'system' | 'us' | 'eu' | 'iso'; + time_format: 'system' | '12h' | '24h'; + // Default printer + default_printer_id: number | null; } export type AppSettingsUpdate = Partial; @@ -362,6 +367,16 @@ export interface SmartPlug { off_temp_threshold: number; username: string | null; password: string | null; + // Power alerts + power_alert_enabled: boolean; + power_alert_high: number | null; + power_alert_low: number | null; + power_alert_last_triggered: string | null; + // Schedule + schedule_enabled: boolean; + schedule_on_time: string | null; + schedule_off_time: string | null; + // Status last_state: string | null; last_checked: string | null; auto_off_executed: boolean; // True when auto-off was triggered after print @@ -381,6 +396,14 @@ export interface SmartPlugCreate { off_temp_threshold?: number; username?: string | null; password?: string | null; + // Power alerts + power_alert_enabled?: boolean; + power_alert_high?: number | null; + power_alert_low?: number | null; + // Schedule + schedule_enabled?: boolean; + schedule_on_time?: string | null; + schedule_off_time?: string | null; } export interface SmartPlugUpdate { @@ -395,6 +418,14 @@ export interface SmartPlugUpdate { off_temp_threshold?: number; username?: string | null; password?: string | null; + // Power alerts + power_alert_enabled?: boolean; + power_alert_high?: number | null; + power_alert_low?: number | null; + // Schedule + schedule_enabled?: boolean; + schedule_on_time?: string | null; + schedule_off_time?: string | null; } export interface SmartPlugEnergy { @@ -552,7 +583,7 @@ export interface Filament { } // Notification Provider types -export type ProviderType = 'callmebot' | 'ntfy' | 'pushover' | 'telegram' | 'email'; +export type ProviderType = 'callmebot' | 'ntfy' | 'pushover' | 'telegram' | 'email' | 'discord' | 'webhook'; export interface NotificationProvider { id: number; @@ -575,6 +606,9 @@ export interface NotificationProvider { quiet_hours_enabled: boolean; quiet_hours_start: string | null; quiet_hours_end: string | null; + // Daily digest + daily_digest_enabled: boolean; + daily_digest_time: string | null; // Printer filter printer_id: number | null; // Status tracking @@ -606,6 +640,9 @@ export interface NotificationProviderCreate { quiet_hours_enabled?: boolean; quiet_hours_start?: string | null; quiet_hours_end?: string | null; + // Daily digest + daily_digest_enabled?: boolean; + daily_digest_time?: string | null; // Printer filter printer_id?: number | null; } @@ -630,6 +667,9 @@ export interface NotificationProviderUpdate { quiet_hours_enabled?: boolean; quiet_hours_start?: string | null; quiet_hours_end?: string | null; + // Daily digest + daily_digest_enabled?: boolean; + daily_digest_time?: string | null; // Printer filter printer_id?: number | null; } @@ -711,6 +751,30 @@ export interface TemplatePreviewResponse { body: string; } +// Notification Log types +export interface NotificationLogEntry { + id: number; + provider_id: number; + provider_name: string | null; + provider_type: string | null; + event_type: string; + title: string; + message: string; + success: boolean; + error_message: string | null; + printer_id: number | null; + printer_name: string | null; + created_at: string; +} + +export interface NotificationLogStats { + total: number; + success_count: number; + failure_count: number; + by_event_type: Record; + by_provider: Record; +} + // Spoolman types export interface SpoolmanStatus { enabled: boolean; @@ -1085,6 +1149,23 @@ export const api = { }), resetSettings: () => request('/settings/reset', { method: 'POST' }), + exportBackup: async () => { + const response = await fetch(`${API_BASE}/settings/backup`); + return response.json(); + }, + importBackup: async (file: File) => { + const formData = new FormData(); + formData.append('file', file); + const response = await fetch(`${API_BASE}/settings/restore`, { + method: 'POST', + body: formData, + }); + return response.json() as Promise<{ + success: boolean; + message: string; + restored?: { settings: number; notification_providers: number; smart_plugs: number }; + }>; + }, checkFfmpeg: () => request<{ installed: boolean; path: string | null }>('/settings/check-ffmpeg'), @@ -1263,6 +1344,19 @@ export const api = { method: 'POST', body: JSON.stringify(data), }), + testAllNotificationProviders: () => + request<{ + tested: number; + success: number; + failed: number; + results: Array<{ + provider_id: number; + provider_name: string; + provider_type: string; + success: boolean; + message: string; + }>; + }>('/notifications/test-all', { method: 'POST' }), // Notification Templates getNotificationTemplates: () => request('/notification-templates'), @@ -1283,6 +1377,32 @@ export const api = { body: JSON.stringify(data), }), + // Notification Logs + getNotificationLogs: (params?: { + limit?: number; + offset?: number; + provider_id?: number; + event_type?: string; + success?: boolean; + days?: number; + }) => { + const searchParams = new URLSearchParams(); + if (params?.limit) searchParams.set('limit', String(params.limit)); + if (params?.offset) searchParams.set('offset', String(params.offset)); + if (params?.provider_id) searchParams.set('provider_id', String(params.provider_id)); + if (params?.event_type) searchParams.set('event_type', params.event_type); + if (params?.success !== undefined) searchParams.set('success', String(params.success)); + if (params?.days) searchParams.set('days', String(params.days)); + return request(`/notifications/logs?${searchParams}`); + }, + getNotificationLogStats: (days = 7) => + request(`/notifications/logs/stats?days=${days}`), + clearNotificationLogs: (olderThanDays = 30) => + request<{ deleted: number; message: string }>( + `/notifications/logs?older_than_days=${olderThanDays}`, + { method: 'DELETE' } + ), + // Spoolman Integration getSpoolmanStatus: () => request('/spoolman/status'), connectSpoolman: () => diff --git a/frontend/src/components/AddNotificationModal.tsx b/frontend/src/components/AddNotificationModal.tsx index 66bf61366..22a0a8028 100644 --- a/frontend/src/components/AddNotificationModal.tsx +++ b/frontend/src/components/AddNotificationModal.tsx @@ -12,11 +12,13 @@ interface AddNotificationModalProps { } const PROVIDER_OPTIONS: { value: ProviderType; label: string; description: string }[] = [ - { value: 'callmebot', label: 'CallMeBot/WhatsApp', description: 'Free WhatsApp notifications via CallMeBot' }, + { value: 'discord', label: 'Discord', description: 'Send to Discord channel via webhook' }, + { value: 'telegram', label: 'Telegram', description: 'Notifications via Telegram bot' }, { value: 'ntfy', label: 'ntfy', description: 'Free, self-hostable push notifications' }, { value: 'pushover', label: 'Pushover', description: 'Simple, reliable push notifications' }, - { value: 'telegram', label: 'Telegram', description: 'Notifications via Telegram bot' }, { value: 'email', label: 'Email', description: 'SMTP email notifications' }, + { value: 'callmebot', label: 'CallMeBot/WhatsApp', description: 'Free WhatsApp notifications via CallMeBot' }, + { value: 'webhook', label: 'Webhook', description: 'Generic HTTP POST to any URL' }, ]; export function AddNotificationModal({ provider, onClose }: AddNotificationModalProps) { @@ -24,12 +26,16 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal const isEditing = !!provider; const [name, setName] = useState(provider?.name || ''); - const [providerType, setProviderType] = useState(provider?.provider_type || 'ntfy'); + const [providerType, setProviderType] = useState(provider?.provider_type || 'discord'); const [printerId, setPrinterId] = useState(provider?.printer_id || null); const [quietHoursEnabled, setQuietHoursEnabled] = useState(provider?.quiet_hours_enabled || false); const [quietHoursStart, setQuietHoursStart] = useState(provider?.quiet_hours_start || '22:00'); const [quietHoursEnd, setQuietHoursEnd] = useState(provider?.quiet_hours_end || '07:00'); + // Daily digest + const [dailyDigestEnabled, setDailyDigestEnabled] = useState(provider?.daily_digest_enabled || false); + const [dailyDigestTime, setDailyDigestTime] = useState(provider?.daily_digest_time || '08:00'); + // Event toggles const [onPrintStart, setOnPrintStart] = useState(provider?.on_print_start ?? false); const [onPrintComplete, setOnPrintComplete] = useState(provider?.on_print_complete ?? true); @@ -126,6 +132,9 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal quiet_hours_enabled: quietHoursEnabled, quiet_hours_start: quietHoursEnabled ? quietHoursStart : null, quiet_hours_end: quietHoursEnabled ? quietHoursEnd : null, + // Daily digest + daily_digest_enabled: dailyDigestEnabled, + daily_digest_time: dailyDigestEnabled ? dailyDigestTime : null, // Event toggles on_print_start: onPrintStart, on_print_complete: onPrintComplete, @@ -190,6 +199,17 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal { key: 'from_email', label: 'From Email', placeholder: 'your@email.com', type: 'text', required: true }, { key: 'to_email', label: 'To Email', placeholder: 'recipient@email.com', type: 'text', required: true }, ]; + case 'discord': + return [ + { key: 'webhook_url', label: 'Webhook URL', placeholder: 'https://discord.com/api/webhooks/...', type: 'text', required: true }, + ]; + case 'webhook': + return [ + { key: 'webhook_url', label: 'Webhook URL', placeholder: 'https://example.com/webhook', type: 'text', required: true }, + { key: 'auth_header', label: 'Authorization', placeholder: 'Bearer token (optional)', type: 'password', required: false }, + { key: 'field_title', label: 'Title Field Name', placeholder: 'title', type: 'text', required: false }, + { key: 'field_message', label: 'Message Field Name', placeholder: 'message', type: 'text', required: false }, + ]; default: return []; } @@ -401,6 +421,34 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal )} + {/* Daily Digest */} +
+
+
+ +

Batch notifications into a single daily summary

+
+ +
+ {dailyDigestEnabled && ( +
+ + setDailyDigestTime(e.target.value)} + className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +

+ Events will be collected and sent as a single summary at this time +

+
+ )} +
+ {/* Event Toggles */}

Notification Events

diff --git a/frontend/src/components/AddSmartPlugModal.tsx b/frontend/src/components/AddSmartPlugModal.tsx index 4e62369b8..0ba245669 100644 --- a/frontend/src/components/AddSmartPlugModal.tsx +++ b/frontend/src/components/AddSmartPlugModal.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; -import { X, Save, Loader2, Wifi, WifiOff, CheckCircle } from 'lucide-react'; +import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock } from 'lucide-react'; import { api } from '../api/client'; import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate } from '../api/client'; import { Button } from './Button'; @@ -22,6 +22,16 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { const [testResult, setTestResult] = useState<{ success: boolean; state?: string | null; device_name?: string | null } | null>(null); const [error, setError] = useState(null); + // Power alert settings + const [powerAlertEnabled, setPowerAlertEnabled] = useState(plug?.power_alert_enabled || false); + const [powerAlertHigh, setPowerAlertHigh] = useState(plug?.power_alert_high?.toString() || ''); + const [powerAlertLow, setPowerAlertLow] = useState(plug?.power_alert_low?.toString() || ''); + + // Schedule settings + const [scheduleEnabled, setScheduleEnabled] = useState(plug?.schedule_enabled || false); + const [scheduleOnTime, setScheduleOnTime] = useState(plug?.schedule_on_time || ''); + const [scheduleOffTime, setScheduleOffTime] = useState(plug?.schedule_off_time || ''); + // Fetch printers for linking const { data: printers } = useQuery({ queryKey: ['printers'], @@ -109,6 +119,14 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) { username: username.trim() || null, password: password.trim() || null, printer_id: printerId, + // Power alerts + power_alert_enabled: powerAlertEnabled, + power_alert_high: powerAlertHigh ? parseFloat(powerAlertHigh) : null, + power_alert_low: powerAlertLow ? parseFloat(powerAlertLow) : null, + // Schedule + schedule_enabled: scheduleEnabled, + schedule_on_time: scheduleOnTime || null, + schedule_off_time: scheduleOffTime || null, }; if (isEditing) { @@ -266,6 +284,104 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {

+ {/* Power Alerts */} +
+
+
+ + Power Alerts +
+ +
+ {powerAlertEnabled && ( +
+
+
+ + setPowerAlertHigh(e.target.value)} + placeholder="e.g. 200" + min="0" + max="5000" + className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
+
+ + setPowerAlertLow(e.target.value)} + placeholder="e.g. 10" + min="0" + max="5000" + className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
+
+

+ Get notified when power consumption crosses these thresholds. Leave empty to disable that direction. +

+
+ )} +
+ + {/* Schedule */} +
+
+
+ + Daily Schedule +
+ +
+ {scheduleEnabled && ( +
+
+
+ + setScheduleOnTime(e.target.value)} + className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
+
+ + setScheduleOffTime(e.target.value)} + className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none" + /> +
+
+

+ Automatically turn the plug on/off at these times daily. Leave empty to skip that action. +

+
+ )} +
+ {/* Actions */}
+
+ + {/* Stats Bar */} + {stats && ( +
+
+ + Last {days} days: {stats.total} notifications + + + + {stats.success_count} sent + + {stats.failure_count > 0 && ( + + + {stats.failure_count} failed + + )} +
+
+ )} + + {/* Filters */} +
+ + + + +
+ + + + +
+ + {/* Log List */} +
+ {isLoading ? ( +
+ +
+ ) : logs && logs.length > 0 ? ( +
+ {logs.map((log) => ( + setExpandedId(expandedId === log.id ? null : log.id)} + formatDate={formatDate} + /> + ))} +
+ ) : ( +
+ +

+ {showFailedOnly ? 'No failed notifications' : 'No notifications logged'} +

+
+ )} +
+
+ + ); +} + +function LogEntry({ + log, + isExpanded, + onToggle, + formatDate, +}: { + log: NotificationLogEntry; + isExpanded: boolean; + onToggle: () => void; + formatDate: (date: string) => string; +}) { + return ( +
+ + + {isExpanded && ( +
+
+

Title

+

{log.title}

+
+
+

Message

+

{log.message}

+
+ {!log.success && log.error_message && ( +
+

Error

+

{log.error_message}

+
+ )} +
+ Provider: {log.provider_type} + Time: {new Date(log.created_at).toLocaleString()} +
+
+ )} +
+ ); +} diff --git a/frontend/src/components/NotificationProviderCard.tsx b/frontend/src/components/NotificationProviderCard.tsx index 77fdc0a7a..509daa1ea 100644 --- a/frontend/src/components/NotificationProviderCard.tsx +++ b/frontend/src/components/NotificationProviderCard.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; -import { Bell, Trash2, Settings2, Edit2, Send, Loader2, CheckCircle, XCircle, Moon, Clock, ChevronDown, ChevronUp } from 'lucide-react'; +import { Bell, Trash2, Settings2, Edit2, Send, Loader2, CheckCircle, XCircle, Moon, Clock, ChevronDown, ChevronUp, Calendar } from 'lucide-react'; import { api } from '../api/client'; import type { NotificationProvider, NotificationProviderUpdate } from '../api/client'; import { Card, CardContent } from './Card'; @@ -19,6 +19,8 @@ const PROVIDER_LABELS: Record = { pushover: 'Pushover', telegram: 'Telegram', email: 'Email', + discord: 'Discord', + webhook: 'Webhook', }; export function NotificationProviderCard({ provider, onEdit }: NotificationProviderCardProps) { @@ -147,6 +149,12 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi Quiet )} + {provider.daily_digest_enabled && ( + + + Digest {provider.daily_digest_time} + + )} {/* Test Button */} @@ -332,6 +340,33 @@ export function NotificationProviderCard({ provider, onEdit }: NotificationProvi )} + {/* Daily Digest */} +
+
+
+ +

Daily Digest

+
+ updateMutation.mutate({ daily_digest_enabled: checked })} + /> +
+ + {provider.daily_digest_enabled && ( +
+

Batch notifications into a single daily summary

+
+ + + Send at {formatTime(provider.daily_digest_time) || '08:00'} + +
+

Edit provider to change digest time

+
+ )} +
+ {/* Action Buttons */}
+
+ + +

Archive Settings

@@ -382,69 +623,6 @@ export function SettingsPage() {
- - - -

{t('settings.general')}

-
- -
- - -

- {t('settings.languageDescription')} -

-
-
- - -

- {t('settings.defaultViewDescription')} -

-
-
-
-

Sidebar order

-

- Drag items in the sidebar to reorder. Reset to default order here. -

-
- -
-
-
{/* Second Column - AMS & Spoolman */} @@ -678,6 +856,121 @@ export function SettingsPage() { + + {/* Data Management */} + + +

Data Management

+
+ + {/* Backup/Restore */} +
+
+

Backup Settings

+

+ Export settings, providers, and plugs to JSON +

+
+ +
+
+
+

Restore Settings

+

+ Import settings from a backup file +

+
+
+ { + const file = e.target.files?.[0]; + if (!file) return; + try { + const result = await api.importBackup(file); + if (result.success) { + showToast(result.message, 'success'); + queryClient.invalidateQueries(); + } else { + showToast(result.message, 'error'); + } + } catch (err) { + showToast('Failed to restore backup', 'error'); + } + e.target.value = ''; + }} + /> + +
+
+ +
+
+
+

Clear Notification Logs

+

+ Delete notification logs older than 30 days +

+
+ +
+
+
+
+

Clear Local Storage

+

+ Clear browser cache (sidebar order, preferences) +

+
+ +
+
+
)} @@ -685,7 +978,7 @@ export function SettingsPage() { {/* Smart Plugs Tab */} {activeTab === 'plugs' && (
-
+

@@ -695,17 +988,144 @@ export function SettingsPage() { Connect Tasmota-based smart plugs to automate power control and track energy usage for your printers.

- +
+ {smartPlugs && smartPlugs.filter(p => p.enabled).length > 1 && ( + <> + + + + )} + +
+ {/* Energy Summary Card */} + {smartPlugs && smartPlugs.length > 0 && ( + + +

+ + Energy Summary + {energyLoading && ( + + )} +

+
+ + {plugEnergySummary ? ( +
+ {/* Current Power */} +
+
+ + Current Power +
+
+ {plugEnergySummary.totalPower.toFixed(1)} + W +
+
+ {plugEnergySummary.reachableCount}/{plugEnergySummary.totalPlugs} plugs online +
+
+ + {/* Today */} +
+
+ + Today +
+
+ {plugEnergySummary.totalToday.toFixed(2)} + kWh +
+ {localSettings && localSettings.energy_cost_per_kwh > 0 && ( +
+ ~{(plugEnergySummary.totalToday * localSettings.energy_cost_per_kwh).toFixed(2)} {localSettings.currency} +
+ )} +
+ + {/* Yesterday */} +
+
+ + Yesterday +
+
+ {plugEnergySummary.totalYesterday.toFixed(2)} + kWh +
+ {localSettings && localSettings.energy_cost_per_kwh > 0 && ( +
+ ~{(plugEnergySummary.totalYesterday * localSettings.energy_cost_per_kwh).toFixed(2)} {localSettings.currency} +
+ )} +
+ + {/* Total Lifetime */} +
+
+ + Total +
+
+ {plugEnergySummary.totalLifetime.toFixed(1)} + kWh +
+ {localSettings && localSettings.energy_cost_per_kwh > 0 && ( +
+ ~{(plugEnergySummary.totalLifetime * localSettings.energy_cost_per_kwh).toFixed(2)} {localSettings.currency} +
+ )} +
+
+ ) : !energyLoading ? ( +

+ Enable plugs to see energy summary +

+ ) : null} +
+
+ )} + {plugsLoading ? (
@@ -756,16 +1176,44 @@ export function SettingsPage() { Providers - +
+ + {notificationProviders && notificationProviders.length > 0 && ( + + )} + +
{/* Notification Language Setting */} @@ -791,6 +1239,44 @@ export function SettingsPage() { + {/* Test All Results */} + {testAllResult && ( + + +
+ Test Results + +
+
+ + + {testAllResult.success} passed + + {testAllResult.failed > 0 && ( + + + {testAllResult.failed} failed + + )} +
+ {testAllResult.results.filter(r => !r.success).length > 0 && ( +
+ {testAllResult.results.filter(r => !r.success).map((result) => ( +
+ {result.provider_name}: {result.message} +
+ ))} +
+ )} +
+
+ )} + {providersLoading ? (
@@ -918,6 +1404,66 @@ export function SettingsPage() { onClose={() => setEditingTemplate(null)} /> )} + + {/* Notification Log Viewer */} + {showLogViewer && ( + setShowLogViewer(false)} + /> + )} + + {/* Confirm Modal: Clear Notification Logs */} + {showClearLogsConfirm && ( + { + setShowClearLogsConfirm(false); + try { + const result = await api.clearNotificationLogs(30); + showToast(result.message, 'success'); + } catch { + showToast('Failed to clear logs', 'error'); + } + }} + onCancel={() => setShowClearLogsConfirm(false)} + /> + )} + + {/* Confirm Modal: Clear Local Storage */} + {showClearStorageConfirm && ( + { + setShowClearStorageConfirm(false); + localStorage.clear(); + showToast('Local storage cleared. Refreshing...', 'success'); + setTimeout(() => window.location.reload(), 1000); + }} + onCancel={() => setShowClearStorageConfirm(false)} + /> + )} + + {/* Confirm Modal: Bulk Plug Action */} + {showBulkPlugConfirm && ( + p.enabled).length || 0} enabled smart plugs. ${showBulkPlugConfirm === 'off' ? 'Any running printers may be affected!' : ''}`} + confirmText={`Turn All ${showBulkPlugConfirm === 'on' ? 'On' : 'Off'}`} + variant={showBulkPlugConfirm === 'off' ? 'danger' : 'warning'} + onConfirm={() => { + const action = showBulkPlugConfirm; + setShowBulkPlugConfirm(null); + bulkPlugActionMutation.mutate(action); + }} + onCancel={() => setShowBulkPlugConfirm(null)} + /> + )}
); } diff --git a/static/assets/index-BomzirkH.css b/static/assets/index-BomzirkH.css deleted file mode 100644 index 0e2e7e925..000000000 --- a/static/assets/index-BomzirkH.css +++ /dev/null @@ -1 +0,0 @@ -@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap";@layer properties,theme,base,components,utilities;@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-widest:.1em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bambu-green:#00ae42;--color-bambu-green-light:#00c64d;--color-bambu-green-dark:#009438;--color-bambu-dark:var(--bg-primary);--color-bambu-dark-secondary:var(--bg-secondary);--color-bambu-dark-tertiary:var(--bg-tertiary);--color-bambu-gray:var(--text-muted);--color-bambu-gray-light:var(--text-secondary);--color-bambu-gray-dark:var(--text-tertiary)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.-top-1{top:calc(var(--spacing)*-1)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing)*2)}.top-4{top:calc(var(--spacing)*4)}.top-\[2px\]{top:2px}.top-full{top:100%}.-right-1{right:calc(var(--spacing)*-1)}.right-0{right:calc(var(--spacing)*0)}.right-2{right:calc(var(--spacing)*2)}.right-4{right:calc(var(--spacing)*4)}.right-6{right:calc(var(--spacing)*6)}.right-12{right:calc(var(--spacing)*12)}.bottom-1{bottom:calc(var(--spacing)*1)}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-6{bottom:calc(var(--spacing)*6)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.left-2\.5{left:calc(var(--spacing)*2.5)}.left-3{left:calc(var(--spacing)*3)}.left-4{left:calc(var(--spacing)*4)}.left-12{left:calc(var(--spacing)*12)}.left-\[2px\]{left:2px}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-60,.z-\[60\]{z-index:60}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.my-1{margin-block:calc(var(--spacing)*1)}.my-8{margin-block:calc(var(--spacing)*8)}.-mt-2{margin-top:calc(var(--spacing)*-2)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-1\.5{margin-bottom:calc(var(--spacing)*1.5)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.-ml-1{margin-left:calc(var(--spacing)*-1)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-1\.5{margin-left:calc(var(--spacing)*1.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-8{margin-left:calc(var(--spacing)*8)}.ml-16{margin-left:calc(var(--spacing)*16)}.ml-64{margin-left:calc(var(--spacing)*64)}.ml-auto{margin-left:auto}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.h-1{height:calc(var(--spacing)*1)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-28{height:calc(var(--spacing)*28)}.h-64{height:calc(var(--spacing)*64)}.h-80{height:calc(var(--spacing)*80)}.h-\[80vh\]{height:80vh}.h-\[160px\]{height:160px}.h-full{height:100%}.max-h-40{max-height:calc(var(--spacing)*40)}.max-h-60{max-height:calc(var(--spacing)*60)}.max-h-96{max-height:calc(var(--spacing)*96)}.max-h-\[60vh\]{max-height:60vh}.max-h-\[80vh\]{max-height:80vh}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[calc\(100vh-320px\)\]{max-height:calc(100vh - 320px)}.max-h-full{max-height:100%}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[48px\]{min-height:48px}.min-h-\[120px\]{min-height:120px}.min-h-\[400px\]{min-height:400px}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-1\/3{width:33.3333%}.w-2{width:calc(var(--spacing)*2)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-7{width:calc(var(--spacing)*7)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-20{width:calc(var(--spacing)*20)}.w-24{width:calc(var(--spacing)*24)}.w-28{width:calc(var(--spacing)*28)}.w-32{width:calc(var(--spacing)*32)}.w-36{width:calc(var(--spacing)*36)}.w-40{width:calc(var(--spacing)*40)}.w-48{width:calc(var(--spacing)*48)}.w-56{width:calc(var(--spacing)*56)}.w-64{width:calc(var(--spacing)*64)}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-32{max-width:calc(var(--spacing)*32)}.max-w-\[90vw\]{max-width:90vw}.max-w-\[100px\]{max-width:100px}.max-w-\[200px\]{max-width:200px}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-48{min-width:calc(var(--spacing)*48)}.min-w-\[80px\]{min-width:80px}.min-w-\[160px\]{min-width:160px}.min-w-\[200px\]{min-width:200px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-0\.5{--tw-translate-x:calc(var(--spacing)*.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-3\.5{--tw-translate-x:calc(var(--spacing)*3.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-\[1\.02\]{scale:1.02}.-rotate-90{rotate:-90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing)*6)}.gap-y-3{row-gap:calc(var(--spacing)*3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-bambu-dark-tertiary>:not(:last-child)){border-color:var(--color-bambu-dark-tertiary)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500\/20{border-color:#f99c0033}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/20{border-color:color-mix(in oklab,var(--color-amber-500)20%,transparent)}}.border-bambu-dark-tertiary{border-color:var(--color-bambu-dark-tertiary)}.border-bambu-green{border-color:var(--color-bambu-green)}.border-bambu-green\/20{border-color:#00ae4233}@supports (color:color-mix(in lab,red,red)){.border-bambu-green\/20{border-color:color-mix(in oklab,var(--color-bambu-green)20%,transparent)}}.border-bambu-green\/30{border-color:#00ae424d}@supports (color:color-mix(in lab,red,red)){.border-bambu-green\/30{border-color:color-mix(in oklab,var(--color-bambu-green)30%,transparent)}}.border-bambu-green\/50{border-color:#00ae4280}@supports (color:color-mix(in lab,red,red)){.border-bambu-green\/50{border-color:color-mix(in oklab,var(--color-bambu-green)50%,transparent)}}.border-blue-400\/20{border-color:#54a2ff33}@supports (color:color-mix(in lab,red,red)){.border-blue-400\/20{border-color:color-mix(in oklab,var(--color-blue-400)20%,transparent)}}.border-blue-500\/20{border-color:#3080ff33}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/20{border-color:color-mix(in oklab,var(--color-blue-500)20%,transparent)}}.border-blue-500\/30{border-color:#3080ff4d}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/30{border-color:color-mix(in oklab,var(--color-blue-500)30%,transparent)}}.border-blue-500\/50{border-color:#3080ff80}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/50{border-color:color-mix(in oklab,var(--color-blue-500)50%,transparent)}}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-400\/20{border-color:#99a1af33}@supports (color:color-mix(in lab,red,red)){.border-gray-400\/20{border-color:color-mix(in oklab,var(--color-gray-400)20%,transparent)}}.border-gray-500\/20{border-color:#6a728233}@supports (color:color-mix(in lab,red,red)){.border-gray-500\/20{border-color:color-mix(in oklab,var(--color-gray-500)20%,transparent)}}.border-gray-700{border-color:var(--color-gray-700)}.border-green-400\/20{border-color:#05df7233}@supports (color:color-mix(in lab,red,red)){.border-green-400\/20{border-color:color-mix(in oklab,var(--color-green-400)20%,transparent)}}.border-green-500\/30{border-color:#00c7584d}@supports (color:color-mix(in lab,red,red)){.border-green-500\/30{border-color:color-mix(in oklab,var(--color-green-500)30%,transparent)}}.border-green-500\/50{border-color:#00c75880}@supports (color:color-mix(in lab,red,red)){.border-green-500\/50{border-color:color-mix(in oklab,var(--color-green-500)50%,transparent)}}.border-orange-400\/20{border-color:#ff8b1a33}@supports (color:color-mix(in lab,red,red)){.border-orange-400\/20{border-color:color-mix(in oklab,var(--color-orange-400)20%,transparent)}}.border-orange-500\/20{border-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.border-orange-500\/20{border-color:color-mix(in oklab,var(--color-orange-500)20%,transparent)}}.border-red-400\/20{border-color:#ff656833}@supports (color:color-mix(in lab,red,red)){.border-red-400\/20{border-color:color-mix(in oklab,var(--color-red-400)20%,transparent)}}.border-red-500{border-color:var(--color-red-500)}.border-red-500\/20{border-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.border-red-500\/20{border-color:color-mix(in oklab,var(--color-red-500)20%,transparent)}}.border-red-500\/30{border-color:#fb2c364d}@supports (color:color-mix(in lab,red,red)){.border-red-500\/30{border-color:color-mix(in oklab,var(--color-red-500)30%,transparent)}}.border-red-500\/50{border-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.border-red-500\/50{border-color:color-mix(in oklab,var(--color-red-500)50%,transparent)}}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.border-white\/20{border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.border-yellow-400\/20{border-color:#fac80033}@supports (color:color-mix(in lab,red,red)){.border-yellow-400\/20{border-color:color-mix(in oklab,var(--color-yellow-400)20%,transparent)}}.border-yellow-500{border-color:var(--color-yellow-500)}.border-yellow-500\/20{border-color:#edb20033}@supports (color:color-mix(in lab,red,red)){.border-yellow-500\/20{border-color:color-mix(in oklab,var(--color-yellow-500)20%,transparent)}}.border-yellow-500\/30{border-color:#edb2004d}@supports (color:color-mix(in lab,red,red)){.border-yellow-500\/30{border-color:color-mix(in oklab,var(--color-yellow-500)30%,transparent)}}.border-yellow-500\/50{border-color:#edb20080}@supports (color:color-mix(in lab,red,red)){.border-yellow-500\/50{border-color:color-mix(in oklab,var(--color-yellow-500)50%,transparent)}}.border-t-transparent{border-top-color:#0000}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-500\/5{background-color:#f99c000d}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/5{background-color:color-mix(in oklab,var(--color-amber-500)5%,transparent)}}.bg-amber-500\/10{background-color:#f99c001a}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/10{background-color:color-mix(in oklab,var(--color-amber-500)10%,transparent)}}.bg-amber-500\/20{background-color:#f99c0033}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/20{background-color:color-mix(in oklab,var(--color-amber-500)20%,transparent)}}.bg-bambu-dark{background-color:var(--color-bambu-dark)}.bg-bambu-dark-secondary,.bg-bambu-dark-secondary\/50{background-color:var(--color-bambu-dark-secondary)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark-secondary\/50{background-color:color-mix(in oklab,var(--color-bambu-dark-secondary)50%,transparent)}}.bg-bambu-dark-secondary\/90{background-color:var(--color-bambu-dark-secondary)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark-secondary\/90{background-color:color-mix(in oklab,var(--color-bambu-dark-secondary)90%,transparent)}}.bg-bambu-dark-tertiary,.bg-bambu-dark-tertiary\/50{background-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark-tertiary\/50{background-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)50%,transparent)}}.bg-bambu-dark\/30{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark\/30{background-color:color-mix(in oklab,var(--color-bambu-dark)30%,transparent)}}.bg-bambu-dark\/50{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark\/50{background-color:color-mix(in oklab,var(--color-bambu-dark)50%,transparent)}}.bg-bambu-dark\/80{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark\/80{background-color:color-mix(in oklab,var(--color-bambu-dark)80%,transparent)}}.bg-bambu-dark\/90{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark\/90{background-color:color-mix(in oklab,var(--color-bambu-dark)90%,transparent)}}.bg-bambu-gray\/20{background-color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-gray\/20{background-color:color-mix(in oklab,var(--color-bambu-gray)20%,transparent)}}.bg-bambu-gray\/30{background-color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-gray\/30{background-color:color-mix(in oklab,var(--color-bambu-gray)30%,transparent)}}.bg-bambu-green{background-color:var(--color-bambu-green)}.bg-bambu-green\/10{background-color:#00ae421a}@supports (color:color-mix(in lab,red,red)){.bg-bambu-green\/10{background-color:color-mix(in oklab,var(--color-bambu-green)10%,transparent)}}.bg-bambu-green\/20{background-color:#00ae4233}@supports (color:color-mix(in lab,red,red)){.bg-bambu-green\/20{background-color:color-mix(in oklab,var(--color-bambu-green)20%,transparent)}}.bg-bambu-green\/30{background-color:#00ae424d}@supports (color:color-mix(in lab,red,red)){.bg-bambu-green\/30{background-color:color-mix(in oklab,var(--color-bambu-green)30%,transparent)}}.bg-bambu-green\/50{background-color:#00ae4280}@supports (color:color-mix(in lab,red,red)){.bg-bambu-green\/50{background-color:color-mix(in oklab,var(--color-bambu-green)50%,transparent)}}.bg-bambu-green\/75{background-color:#00ae42bf}@supports (color:color-mix(in lab,red,red)){.bg-bambu-green\/75{background-color:color-mix(in oklab,var(--color-bambu-green)75%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black)70%,transparent)}}.bg-black\/80{background-color:#000c}@supports (color:color-mix(in lab,red,red)){.bg-black\/80{background-color:color-mix(in oklab,var(--color-black)80%,transparent)}}.bg-black\/90{background-color:#000000e6}@supports (color:color-mix(in lab,red,red)){.bg-black\/90{background-color:color-mix(in oklab,var(--color-black)90%,transparent)}}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-400\/10{background-color:#54a2ff1a}@supports (color:color-mix(in lab,red,red)){.bg-blue-400\/10{background-color:color-mix(in oklab,var(--color-blue-400)10%,transparent)}}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/10{background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.bg-blue-500\/20{background-color:#3080ff33}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/20{background-color:color-mix(in oklab,var(--color-blue-500)20%,transparent)}}.bg-blue-500\/30{background-color:#3080ff4d}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/30{background-color:color-mix(in oklab,var(--color-blue-500)30%,transparent)}}.bg-cyan-500\/20{background-color:#00b7d733}@supports (color:color-mix(in lab,red,red)){.bg-cyan-500\/20{background-color:color-mix(in oklab,var(--color-cyan-500)20%,transparent)}}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-400\/10{background-color:#99a1af1a}@supports (color:color-mix(in lab,red,red)){.bg-gray-400\/10{background-color:color-mix(in oklab,var(--color-gray-400)10%,transparent)}}.bg-gray-500\/20{background-color:#6a728233}@supports (color:color-mix(in lab,red,red)){.bg-gray-500\/20{background-color:color-mix(in oklab,var(--color-gray-500)20%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-400\/10{background-color:#05df721a}@supports (color:color-mix(in lab,red,red)){.bg-green-400\/10{background-color:color-mix(in oklab,var(--color-green-400)10%,transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-green-500\/10{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/10{background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.bg-green-500\/20{background-color:#00c75833}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/20{background-color:color-mix(in oklab,var(--color-green-500)20%,transparent)}}.bg-indigo-500\/20{background-color:#625fff33}@supports (color:color-mix(in lab,red,red)){.bg-indigo-500\/20{background-color:color-mix(in oklab,var(--color-indigo-500)20%,transparent)}}.bg-orange-400\/10{background-color:#ff8b1a1a}@supports (color:color-mix(in lab,red,red)){.bg-orange-400\/10{background-color:color-mix(in oklab,var(--color-orange-400)10%,transparent)}}.bg-orange-500\/10{background-color:#fe6e001a}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/10{background-color:color-mix(in oklab,var(--color-orange-500)10%,transparent)}}.bg-orange-500\/20{background-color:#fe6e0033}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/20{background-color:color-mix(in oklab,var(--color-orange-500)20%,transparent)}}.bg-purple-500\/20{background-color:#ac4bff33}@supports (color:color-mix(in lab,red,red)){.bg-purple-500\/20{background-color:color-mix(in oklab,var(--color-purple-500)20%,transparent)}}.bg-purple-500\/80{background-color:#ac4bffcc}@supports (color:color-mix(in lab,red,red)){.bg-purple-500\/80{background-color:color-mix(in oklab,var(--color-purple-500)80%,transparent)}}.bg-red-400{background-color:var(--color-red-400)}.bg-red-400\/10{background-color:#ff65681a}@supports (color:color-mix(in lab,red,red)){.bg-red-400\/10{background-color:color-mix(in oklab,var(--color-red-400)10%,transparent)}}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/5{background-color:#fb2c360d}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/5{background-color:color-mix(in oklab,var(--color-red-500)5%,transparent)}}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/10{background-color:color-mix(in oklab,var(--color-red-500)10%,transparent)}}.bg-red-500\/15{background-color:#fb2c3626}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/15{background-color:color-mix(in oklab,var(--color-red-500)15%,transparent)}}.bg-red-500\/20{background-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/20{background-color:color-mix(in oklab,var(--color-red-500)20%,transparent)}}.bg-red-500\/30{background-color:#fb2c364d}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/30{background-color:color-mix(in oklab,var(--color-red-500)30%,transparent)}}.bg-red-500\/80{background-color:#fb2c36cc}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/80{background-color:color-mix(in oklab,var(--color-red-500)80%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-rose-500\/20{background-color:#ff235733}@supports (color:color-mix(in lab,red,red)){.bg-rose-500\/20{background-color:color-mix(in oklab,var(--color-rose-500)20%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-400\/10{background-color:#fac8001a}@supports (color:color-mix(in lab,red,red)){.bg-yellow-400\/10{background-color:color-mix(in oklab,var(--color-yellow-400)10%,transparent)}}.bg-yellow-400\/20{background-color:#fac80033}@supports (color:color-mix(in lab,red,red)){.bg-yellow-400\/20{background-color:color-mix(in oklab,var(--color-yellow-400)20%,transparent)}}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-500\/10{background-color:#edb2001a}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/10{background-color:color-mix(in oklab,var(--color-yellow-500)10%,transparent)}}.bg-yellow-500\/20{background-color:#edb20033}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/20{background-color:color-mix(in oklab,var(--color-yellow-500)20%,transparent)}}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-bambu-green\/10{--tw-gradient-from:#00ae421a}@supports (color:color-mix(in lab,red,red)){.from-bambu-green\/10{--tw-gradient-from:color-mix(in oklab,var(--color-bambu-green)10%,transparent)}}.from-bambu-green\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-blue-500\/5{--tw-gradient-from:#3080ff0d}@supports (color:color-mix(in lab,red,red)){.from-blue-500\/5{--tw-gradient-from:color-mix(in oklab,var(--color-blue-500)5%,transparent)}}.from-blue-500\/5{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-blue-500\/10{--tw-gradient-from:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.from-blue-500\/10{--tw-gradient-from:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.from-blue-500\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-gray-500\/10{--tw-gradient-from:#6a72821a}@supports (color:color-mix(in lab,red,red)){.from-gray-500\/10{--tw-gradient-from:color-mix(in oklab,var(--color-gray-500)10%,transparent)}}.from-gray-500\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-yellow-500\/10{--tw-gradient-from:#edb2001a}@supports (color:color-mix(in lab,red,red)){.from-yellow-500\/10{--tw-gradient-from:color-mix(in oklab,var(--color-yellow-500)10%,transparent)}}.from-yellow-500\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-blue-400{--tw-gradient-to:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.fill-yellow-400{fill:var(--color-yellow-400)}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.object-left{object-position:left}.p-0{padding:calc(var(--spacing)*0)}.p-0\.5{padding:calc(var(--spacing)*.5)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-2\.5{padding:calc(var(--spacing)*2.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.\!px-3{padding-inline:calc(var(--spacing)*3)!important}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-0{padding-top:calc(var(--spacing)*0)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-8{padding-left:calc(var(--spacing)*8)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-400\/70{color:#fcbb00b3}@supports (color:color-mix(in lab,red,red)){.text-amber-400\/70{color:color-mix(in oklab,var(--color-amber-400)70%,transparent)}}.text-amber-600{color:var(--color-amber-600)}.text-bambu-dark-tertiary{color:var(--color-bambu-dark-tertiary)}.text-bambu-gray{color:var(--color-bambu-gray)}.text-bambu-gray-dark{color:var(--color-bambu-gray-dark)}.text-bambu-gray-light{color:var(--color-bambu-gray-light)}.text-bambu-gray\/30{color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.text-bambu-gray\/30{color:color-mix(in oklab,var(--color-bambu-gray)30%,transparent)}}.text-bambu-gray\/50{color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.text-bambu-gray\/50{color:color-mix(in oklab,var(--color-bambu-gray)50%,transparent)}}.text-bambu-gray\/60{color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.text-bambu-gray\/60{color:color-mix(in oklab,var(--color-bambu-gray)60%,transparent)}}.text-bambu-gray\/70{color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.text-bambu-gray\/70{color:color-mix(in oklab,var(--color-bambu-gray)70%,transparent)}}.text-bambu-green{color:var(--color-bambu-green)}.text-bambu-green\/70{color:#00ae42b3}@supports (color:color-mix(in lab,red,red)){.text-bambu-green\/70{color:color-mix(in oklab,var(--color-bambu-green)70%,transparent)}}.text-blue-400{color:var(--color-blue-400)}.text-blue-700{color:var(--color-blue-700)}.text-cyan-400{color:var(--color-cyan-400)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-900{color:var(--color-gray-900)}.text-green-300{color:var(--color-green-300)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-indigo-400{color:var(--color-indigo-400)}.text-orange-400{color:var(--color-orange-400)}.text-orange-600{color:var(--color-orange-600)}.text-purple-400{color:var(--color-purple-400)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-400\/70{color:#ff6568b3}@supports (color:color-mix(in lab,red,red)){.text-red-400\/70{color:color-mix(in oklab,var(--color-red-400)70%,transparent)}}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-rose-400{color:var(--color-rose-400)}.text-white{color:var(--color-white)}.text-white\/80{color:#fffc}@supports (color:color-mix(in lab,red,red)){.text-white\/80{color:color-mix(in oklab,var(--color-white)80%,transparent)}}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-500\/70{color:#edb200b3}@supports (color:color-mix(in lab,red,red)){.text-yellow-500\/70{color:color-mix(in oklab,var(--color-yellow-500)70%,transparent)}}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.placeholder-bambu-gray::placeholder{color:var(--color-bambu-gray)}.placeholder-bambu-gray-dark::placeholder{color:var(--color-bambu-gray-dark)}.placeholder-bambu-gray\/50::placeholder{color:var(--color-bambu-gray)}@supports (color:color-mix(in lab,red,red)){.placeholder-bambu-gray\/50::placeholder{color:color-mix(in oklab,var(--color-bambu-gray)50%,transparent)}}.accent-bambu-green{accent-color:var(--color-bambu-green)}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-bambu-green\/25{--tw-shadow-color:#00ae4240}@supports (color:color-mix(in lab,red,red)){.shadow-bambu-green\/25{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-bambu-green)25%,transparent)var(--tw-shadow-alpha),transparent)}}.ring-bambu-green{--tw-ring-color:var(--color-bambu-green)}.ring-white{--tw-ring-color:var(--color-white)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media(hover:hover){.group-hover\:bg-bambu-green\/20:is(:where(.group):hover *){background-color:#00ae4233}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-bambu-green\/20:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-bambu-green)20%,transparent)}}.group-hover\:text-bambu-green:is(:where(.group):hover *){color:var(--color-bambu-green)}.group-hover\:opacity-50:is(:where(.group):hover *){opacity:.5}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.peer-checked\:bg-bambu-green:is(:where(.peer):checked~*){background-color:var(--color-bambu-green)}.peer-focus\:outline-none:is(:where(.peer):focus~*){--tw-outline-style:none;outline-style:none}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:top-0:before{content:var(--tw-content);top:calc(var(--spacing)*0)}.before\:right-0:before{content:var(--tw-content);right:calc(var(--spacing)*0)}.before\:left-0:before{content:var(--tw-content);left:calc(var(--spacing)*0)}.before\:h-0\.5:before{content:var(--tw-content);height:calc(var(--spacing)*.5)}.before\:bg-bambu-green:before{content:var(--tw-content);background-color:var(--color-bambu-green)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:top-\[2px\]:after{content:var(--tw-content);top:2px}.after\:left-\[2px\]:after{content:var(--tw-content);left:2px}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing)*4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing)*5)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing)*4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing)*5)}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:bg-white:after{content:var(--tw-content);background-color:var(--color-white)}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.peer-checked\:after\:translate-x-full:is(:where(.peer):checked~*):after{content:var(--tw-content);--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.peer-checked\:after\:border-white:is(:where(.peer):checked~*):after{content:var(--tw-content);border-color:var(--color-white)}@media(hover:hover){.hover\:border-bambu-dark-tertiary\/80:hover{border-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.hover\:border-bambu-dark-tertiary\/80:hover{border-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)80%,transparent)}}.hover\:border-bambu-gray:hover{border-color:var(--color-bambu-gray)}.hover\:border-bambu-gray-dark:hover{border-color:var(--color-bambu-gray-dark)}.hover\:border-bambu-green:hover{border-color:var(--color-bambu-green)}.hover\:border-bambu-green\/50:hover{border-color:#00ae4280}@supports (color:color-mix(in lab,red,red)){.hover\:border-bambu-green\/50:hover{border-color:color-mix(in oklab,var(--color-bambu-green)50%,transparent)}}.hover\:border-white\/40:hover{border-color:#fff6}@supports (color:color-mix(in lab,red,red)){.hover\:border-white\/40:hover{border-color:color-mix(in oklab,var(--color-white)40%,transparent)}}.hover\:bg-bambu-dark:hover{background-color:var(--color-bambu-dark)}.hover\:bg-bambu-dark-secondary:hover{background-color:var(--color-bambu-dark-secondary)}.hover\:bg-bambu-dark-tertiary:hover,.hover\:bg-bambu-dark-tertiary\/30:hover{background-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-dark-tertiary\/30:hover{background-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)30%,transparent)}}.hover\:bg-bambu-dark-tertiary\/50:hover{background-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-dark-tertiary\/50:hover{background-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)50%,transparent)}}.hover\:bg-bambu-dark-tertiary\/70:hover{background-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-dark-tertiary\/70:hover{background-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)70%,transparent)}}.hover\:bg-bambu-dark-tertiary\/80:hover{background-color:var(--color-bambu-dark-tertiary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-dark-tertiary\/80:hover{background-color:color-mix(in oklab,var(--color-bambu-dark-tertiary)80%,transparent)}}.hover\:bg-bambu-gray-dark:hover{background-color:var(--color-bambu-gray-dark)}.hover\:bg-bambu-green:hover{background-color:var(--color-bambu-green)}.hover\:bg-bambu-green-dark:hover{background-color:var(--color-bambu-green-dark)}.hover\:bg-bambu-green-light:hover{background-color:var(--color-bambu-green-light)}.hover\:bg-bambu-green\/10:hover{background-color:#00ae421a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-green\/10:hover{background-color:color-mix(in oklab,var(--color-bambu-green)10%,transparent)}}.hover\:bg-bambu-green\/20:hover{background-color:#00ae4233}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-green\/20:hover{background-color:color-mix(in oklab,var(--color-bambu-green)20%,transparent)}}.hover\:bg-bambu-green\/30:hover{background-color:#00ae424d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-green\/30:hover{background-color:color-mix(in oklab,var(--color-bambu-green)30%,transparent)}}.hover\:bg-bambu-green\/80:hover{background-color:#00ae42cc}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-green\/80:hover{background-color:color-mix(in oklab,var(--color-bambu-green)80%,transparent)}}.hover\:bg-black\/70:hover{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/70:hover{background-color:color-mix(in oklab,var(--color-black)70%,transparent)}}.hover\:bg-black\/80:hover{background-color:#000c}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/80:hover{background-color:color-mix(in oklab,var(--color-black)80%,transparent)}}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-red-400\/10:hover{background-color:#ff65681a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-400\/10:hover{background-color:color-mix(in oklab,var(--color-red-400)10%,transparent)}}.hover\:bg-red-500\/10:hover{background-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-500\/10:hover{background-color:color-mix(in oklab,var(--color-red-500)10%,transparent)}}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:text-bambu-green:hover{color:var(--color-bambu-green)}.hover\:text-bambu-green-light:hover{color:var(--color-bambu-green-light)}.hover\:text-bambu-green\/70:hover{color:#00ae42b3}@supports (color:color-mix(in lab,red,red)){.hover\:text-bambu-green\/70:hover{color:color-mix(in oklab,var(--color-bambu-green)70%,transparent)}}.hover\:text-bambu-green\/80:hover{color:#00ae42cc}@supports (color:color-mix(in lab,red,red)){.hover\:text-bambu-green\/80:hover{color:color-mix(in oklab,var(--color-bambu-green)80%,transparent)}}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-orange-300:hover{color:var(--color-orange-300)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-70:hover{opacity:.7}.hover\:opacity-80:hover{opacity:.8}}.focus\:border-bambu-green:focus{border-color:var(--color-bambu-green)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-bambu-gray:focus{--tw-ring-color:var(--color-bambu-gray)}.focus\:ring-bambu-green:focus{--tw-ring-color:var(--color-bambu-green)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:ring-offset-bambu-dark:focus{--tw-ring-offset-color:var(--color-bambu-dark)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:inline{display:inline}.sm\:h-4{height:calc(var(--spacing)*4)}.sm\:w-4{width:calc(var(--spacing)*4)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:p-1\.5{padding:calc(var(--spacing)*1.5)}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}@media(min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:w-80{width:calc(var(--spacing)*80)}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:p-8{padding:calc(var(--spacing)*8)}}@media(min-width:80rem){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.dark\:border-bambu-dark-tertiary:where(.dark,.dark *){border-color:var(--color-bambu-dark-tertiary)}.dark\:bg-bambu-dark-secondary:where(.dark,.dark *){background-color:var(--color-bambu-dark-secondary)}.dark\:text-bambu-gray:where(.dark,.dark *){color:var(--color-bambu-gray)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-white:where(.dark,.dark *){color:var(--color-white)}@media(hover:hover){.dark\:hover\:bg-bambu-dark-tertiary:where(.dark,.dark *):hover{background-color:var(--color-bambu-dark-tertiary)}.dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--color-white)}}.\[\&\:\:-webkit-slider-thumb\]\:h-3::-webkit-slider-thumb{height:calc(var(--spacing)*3)}.\[\&\:\:-webkit-slider-thumb\]\:w-3::-webkit-slider-thumb{width:calc(var(--spacing)*3)}.\[\&\:\:-webkit-slider-thumb\]\:cursor-pointer::-webkit-slider-thumb{cursor:pointer}.\[\&\:\:-webkit-slider-thumb\]\:appearance-none::-webkit-slider-thumb{appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:rounded-full::-webkit-slider-thumb{border-radius:3.40282e38px}.\[\&\:\:-webkit-slider-thumb\]\:bg-bambu-green::-webkit-slider-thumb{background-color:var(--color-bambu-green)}}:root{--bg-primary:#f5f5f5;--bg-secondary:#fff;--bg-tertiary:#e5e5e5;--text-primary:#1a1a1a;--text-secondary:#4a4a4a;--text-muted:#6b6b6b;--text-tertiary:gray;--border-color:#d4d4d4;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,system-ui,sans-serif;font-weight:400;line-height:1.5}.dark{--bg-primary:#1a1a1a;--bg-secondary:#2d2d2d;--bg-tertiary:#3d3d3d;--text-primary:#fff;--text-secondary:#a0a0a0;--text-muted:gray;--text-tertiary:#4a4a4a;--border-color:#3d3d3d}body{background-color:var(--bg-primary);color:var(--text-primary);min-height:100vh;margin:0;transition:background-color .2s,color .2s}#root{min-height:100vh}.text-white{color:var(--text-primary)}.bg-bambu-dark,.bg-bambu-dark-secondary,.bg-bambu-dark-tertiary,.border-bambu-dark-tertiary{transition:background-color .2s,border-color .2s}@keyframes slide-in{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.animate-slide-in{animation:.2s ease-out slide-in}.icon-theme{opacity:.5}.dark .icon-theme{filter:invert();opacity:.4}.icon-green{filter:invert(48%)sepia(89%)saturate(459%)hue-rotate(93deg)brightness(95%)contrast(92%);opacity:1}.icon-heating{filter:invert(50%)sepia()saturate(1000%)hue-rotate(360deg)brightness()contrast();opacity:1}.dark .jogpad-theme{filter:brightness(.35)contrast(1.2)}.ams-empty-slot{background:repeating-linear-gradient(45deg,#444,#444 2px,#222 2px 4px)}.dark .ams-empty-slot{background:repeating-linear-gradient(45deg,#555,#555 2px,#333 2px 4px)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}} diff --git a/static/assets/index-C99F9s0L.js b/static/assets/index-C99F9s0L.js deleted file mode 100644 index c9b53f5ec..000000000 --- a/static/assets/index-C99F9s0L.js +++ /dev/null @@ -1,8611 +0,0 @@ -function bne(t,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var Ub=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Ja(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var OA={exports:{}},jg={};var sU;function yne(){if(sU)return jg;sU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,s){var a=null;if(s!==void 0&&(a=""+s),i.key!==void 0&&(a=""+i.key),"key"in i){s={};for(var o in i)o!=="key"&&(s[o]=i[o])}else s=i;return i=s.ref,{$$typeof:t,type:r,key:a,ref:i!==void 0?i:null,props:s}}return jg.Fragment=e,jg.jsx=n,jg.jsxs=n,jg}var aU;function _ne(){return aU||(aU=1,OA.exports=yne()),OA.exports}var f=_ne(),LA={exports:{}},Zt={};var oU;function Sne(){if(oU)return Zt;oU=1;var t=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),a=Symbol.for("react.context"),o=Symbol.for("react.forward_ref"),l=Symbol.for("react.suspense"),c=Symbol.for("react.memo"),u=Symbol.for("react.lazy"),h=Symbol.for("react.activity"),g=Symbol.iterator;function m(H){return H===null||typeof H!="object"?null:(H=g&&H[g]||H["@@iterator"],typeof H=="function"?H:null)}var b={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,v={};function x(H,Y,te){this.props=H,this.context=Y,this.refs=v,this.updater=te||b}x.prototype.isReactComponent={},x.prototype.setState=function(H,Y){if(typeof H!="object"&&typeof H!="function"&&H!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,H,Y,"setState")},x.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function M(){}M.prototype=x.prototype;function w(H,Y,te){this.props=H,this.context=Y,this.refs=v,this.updater=te||b}var T=w.prototype=new M;T.constructor=w,y(T,x.prototype),T.isPureReactComponent=!0;var A=Array.isArray;function P(){}var C={H:null,A:null,T:null,S:null},j=Object.prototype.hasOwnProperty;function N(H,Y,te){var ae=te.ref;return{$$typeof:t,type:H,key:Y,ref:ae!==void 0?ae:null,props:te}}function k(H,Y){return N(H.type,Y,H.props)}function F(H){return typeof H=="object"&&H!==null&&H.$$typeof===t}function I(H){var Y={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(te){return Y[te]})}var W=/\/+/g;function D(H,Y){return typeof H=="object"&&H!==null&&H.key!=null?I(""+H.key):Y.toString(36)}function $(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(P,P):(H.status="pending",H.then(function(Y){H.status==="pending"&&(H.status="fulfilled",H.value=Y)},function(Y){H.status==="pending"&&(H.status="rejected",H.reason=Y)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function G(H,Y,te,ae,fe){var Q=typeof H;(Q==="undefined"||Q==="boolean")&&(H=null);var me=!1;if(H===null)me=!0;else switch(Q){case"bigint":case"string":case"number":me=!0;break;case"object":switch(H.$$typeof){case t:case e:me=!0;break;case u:return me=H._init,G(me(H._payload),Y,te,ae,fe)}}if(me)return fe=fe(H),me=ae===""?"."+D(H,0):ae,A(fe)?(te="",me!=null&&(te=me.replace(W,"$&/")+"/"),G(fe,Y,te,"",function(we){return we})):fe!=null&&(F(fe)&&(fe=k(fe,te+(fe.key==null||H&&H.key===fe.key?"":(""+fe.key).replace(W,"$&/")+"/")+me)),Y.push(fe)),1;me=0;var Ee=ae===""?".":ae+":";if(A(H))for(var he=0;he>>1,ee=G[K];if(0>>1;Ki(te,Z))aei(fe,te)?(G[K]=fe,G[ae]=Z,K=ae):(G[K]=te,G[Y]=Z,K=Y);else if(aei(fe,Z))G[K]=fe,G[ae]=Z,K=ae;else break e}}return V}function i(G,V){var Z=G.sortIndex-V.sortIndex;return Z!==0?Z:G.id-V.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var s=performance;t.unstable_now=function(){return s.now()}}else{var a=Date,o=a.now();t.unstable_now=function(){return a.now()-o}}var l=[],c=[],u=1,h=null,g=3,m=!1,b=!1,y=!1,v=!1,x=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,w=typeof setImmediate<"u"?setImmediate:null;function T(G){for(var V=n(c);V!==null;){if(V.callback===null)r(c);else if(V.startTime<=G)r(c),V.sortIndex=V.expirationTime,e(l,V);else break;V=n(c)}}function A(G){if(y=!1,T(G),!b)if(n(l)!==null)b=!0,P||(P=!0,I());else{var V=n(c);V!==null&&$(A,V.startTime-G)}}var P=!1,C=-1,j=5,N=-1;function k(){return v?!0:!(t.unstable_now()-NG&&k());){var K=h.callback;if(typeof K=="function"){h.callback=null,g=h.priorityLevel;var ee=K(h.expirationTime<=G);if(G=t.unstable_now(),typeof ee=="function"){h.callback=ee,T(G),V=!0;break t}h===n(l)&&r(l),T(G)}else r(l);h=n(l)}if(h!==null)V=!0;else{var H=n(c);H!==null&&$(A,H.startTime-G),V=!1}}break e}finally{h=null,g=Z,m=!1}V=void 0}}finally{V?I():P=!1}}}var I;if(typeof w=="function")I=function(){w(F)};else if(typeof MessageChannel<"u"){var W=new MessageChannel,D=W.port2;W.port1.onmessage=F,I=function(){D.postMessage(null)}}else I=function(){x(F,0)};function $(G,V){C=x(function(){G(t.unstable_now())},V)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(G){G.callback=null},t.unstable_forceFrameRate=function(G){0>G||125K?(G.sortIndex=Z,e(c,G),n(l)===null&&G===n(c)&&(y?(M(C),C=-1):y=!0,$(A,Z-K))):(G.sortIndex=ee,e(l,G),b||m||(b=!0,P||(P=!0,I()))),G},t.unstable_shouldYield=k,t.unstable_wrapCallback=function(G){var V=g;return function(){var Z=g;g=V;try{return G.apply(this,arguments)}finally{g=Z}}}})(UA)),UA}var uU;function Ene(){return uU||(uU=1,jA.exports=Mne()),jA.exports}var FA={exports:{}},Zi={};var dU;function Ane(){if(dU)return Zi;dU=1;var t=Mm();function e(l){var c="https://react.dev/errors/"+l;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),FA.exports=Ane(),FA.exports}var hU;function Tne(){if(hU)return Ug;hU=1;var t=Ene(),e=Mm(),n=SG();function r(d){var p="https://react.dev/errors/"+d;if(1ee||(d.current=K[ee],K[ee]=null,ee--)}function te(d,p){ee++,K[ee]=d.current,d.current=p}var ae=H(null),fe=H(null),Q=H(null),me=H(null);function Ee(d,p){switch(te(Q,p),te(fe,d),te(ae,null),p.nodeType){case 9:case 11:d=(d=p.documentElement)&&(d=d.namespaceURI)?C4(d):0;break;default:if(d=p.tagName,p=p.namespaceURI)p=C4(p),d=N4(p,d);else switch(d){case"svg":d=1;break;case"math":d=2;break;default:d=0}}Y(ae),te(ae,d)}function he(){Y(ae),Y(fe),Y(Q)}function we(d){d.memoizedState!==null&&te(me,d);var p=ae.current,_=N4(p,d.type);p!==_&&(te(fe,d),te(ae,_))}function De(d){fe.current===d&&(Y(ae),Y(fe)),me.current===d&&(Y(me),Dg._currentValue=Z)}var de,Le;function B(d){if(de===void 0)try{throw Error()}catch(_){var p=_.stack.trim().match(/\n( *(at )?)/);de=p&&p[1]||"",Le=-1<_.stack.indexOf(` - at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` -`+de+d+Le}var z=!1;function ie(d,p){if(!d||z)return"";z=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var S={DetermineComponentFrameRoot:function(){try{if(p){var ut=function(){throw Error()};if(Object.defineProperty(ut.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ut,[])}catch(rt){var qe=rt}Reflect.construct(d,[],ut)}else{try{ut.call()}catch(rt){qe=rt}d.call(ut.prototype)}}else{try{throw Error()}catch(rt){qe=rt}(ut=d())&&typeof ut.catch=="function"&&ut.catch(function(){})}}catch(rt){if(rt&&qe&&typeof rt.stack=="string")return[rt.stack,qe.stack]}return[null,null]}};S.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var R=Object.getOwnPropertyDescriptor(S.DetermineComponentFrameRoot,"name");R&&R.configurable&&Object.defineProperty(S.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var O=S.DetermineComponentFrameRoot(),q=O[0],oe=O[1];if(q&&oe){var Se=q.split(` -`),$e=oe.split(` -`);for(R=S=0;SR||Se[S]!==$e[R]){var at=` -`+Se[S].replace(" at new "," at ");return d.displayName&&at.includes("")&&(at=at.replace("",d.displayName)),at}while(1<=S&&0<=R);break}}}finally{z=!1,Error.prepareStackTrace=_}return(_=d?d.displayName||d.name:"")?B(_):""}function X(d,p){switch(d.tag){case 26:case 27:case 5:return B(d.type);case 16:return B("Lazy");case 13:return d.child!==p&&p!==null?B("Suspense Fallback"):B("Suspense");case 19:return B("SuspenseList");case 0:case 15:return ie(d.type,!1);case 11:return ie(d.type.render,!1);case 1:return ie(d.type,!0);case 31:return B("Activity");default:return""}}function J(d){try{var p="",_=null;do p+=X(d,_),_=d,d=d.return;while(d);return p}catch(S){return` -Error generating stack: `+S.message+` -`+S.stack}}var le=Object.prototype.hasOwnProperty,Re=t.unstable_scheduleCallback,Ae=t.unstable_cancelCallback,se=t.unstable_shouldYield,U=t.unstable_requestPaint,L=t.unstable_now,ne=t.unstable_getCurrentPriorityLevel,ve=t.unstable_ImmediatePriority,ye=t.unstable_UserBlockingPriority,xe=t.unstable_NormalPriority,tt=t.unstable_LowPriority,je=t.unstable_IdlePriority,Ye=t.log,ot=t.unstable_setDisableYieldValue,Fe=null,ce=null;function Qe(d){if(typeof Ye=="function"&&ot(d),ce&&typeof ce.setStrictMode=="function")try{ce.setStrictMode(Fe,d)}catch{}}var Be=Math.clz32?Math.clz32:ue,Ke=Math.log,ct=Math.LN2;function ue(d){return d>>>=0,d===0?32:31-(Ke(d)/ct|0)|0}var Je=256,ge=262144,Ie=4194304;function Te(d){var p=d&42;if(p!==0)return p;switch(d&-d){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return d&261888;case 262144:case 524288:case 1048576:case 2097152:return d&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return d&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return d}}function be(d,p,_){var S=d.pendingLanes;if(S===0)return 0;var R=0,O=d.suspendedLanes,q=d.pingedLanes;d=d.warmLanes;var oe=S&134217727;return oe!==0?(S=oe&~O,S!==0?R=Te(S):(q&=oe,q!==0?R=Te(q):_||(_=oe&~d,_!==0&&(R=Te(_))))):(oe=S&~O,oe!==0?R=Te(oe):q!==0?R=Te(q):_||(_=S&~d,_!==0&&(R=Te(_)))),R===0?0:p!==0&&p!==R&&(p&O)===0&&(O=R&-R,_=p&-p,O>=_||O===32&&(_&4194048)!==0)?p:R}function Ve(d,p){return(d.pendingLanes&~(d.suspendedLanes&~d.pingedLanes)&p)===0}function ft(d,p){switch(d){case 1:case 2:case 4:case 8:case 64:return p+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return p+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function yt(){var d=Ie;return Ie<<=1,(Ie&62914560)===0&&(Ie=4194304),d}function Pt(d){for(var p=[],_=0;31>_;_++)p.push(d);return p}function cn(d,p){d.pendingLanes|=p,p!==268435456&&(d.suspendedLanes=0,d.pingedLanes=0,d.warmLanes=0)}function Ot(d,p,_,S,R,O){var q=d.pendingLanes;d.pendingLanes=_,d.suspendedLanes=0,d.pingedLanes=0,d.warmLanes=0,d.expiredLanes&=_,d.entangledLanes&=_,d.errorRecoveryDisabledLanes&=_,d.shellSuspendCounter=0;var oe=d.entanglements,Se=d.expirationTimes,$e=d.hiddenUpdates;for(_=q&~_;0<_;){var at=31-Be(_),ut=1<"u")return null;try{return d.activeElement||d.body}catch{return d.body}}var Nr=/[\n"\\]/g;function Nn(d){return d.replace(Nr,function(p){return"\\"+p.charCodeAt(0).toString(16)+" "})}function no(d,p,_,S,R,O,q,oe){d.name="",q!=null&&typeof q!="function"&&typeof q!="symbol"&&typeof q!="boolean"?d.type=q:d.removeAttribute("type"),p!=null?q==="number"?(p===0&&d.value===""||d.value!=p)&&(d.value=""+dn(p)):d.value!==""+dn(p)&&(d.value=""+dn(p)):q!=="submit"&&q!=="reset"||d.removeAttribute("value"),p!=null?vi(d,q,dn(p)):_!=null?vi(d,q,dn(_)):S!=null&&d.removeAttribute("value"),R==null&&O!=null&&(d.defaultChecked=!!O),R!=null&&(d.checked=R&&typeof R!="function"&&typeof R!="symbol"),oe!=null&&typeof oe!="function"&&typeof oe!="symbol"&&typeof oe!="boolean"?d.name=""+dn(oe):d.removeAttribute("name")}function kn(d,p,_,S,R,O,q,oe){if(O!=null&&typeof O!="function"&&typeof O!="symbol"&&typeof O!="boolean"&&(d.type=O),p!=null||_!=null){if(!(O!=="submit"&&O!=="reset"||p!=null)){Yt(d);return}_=_!=null?""+dn(_):"",p=p!=null?""+dn(p):_,oe||p===d.value||(d.value=p),d.defaultValue=p}S=S??R,S=typeof S!="function"&&typeof S!="symbol"&&!!S,d.checked=oe?d.checked:!!S,d.defaultChecked=!!S,q!=null&&typeof q!="function"&&typeof q!="symbol"&&typeof q!="boolean"&&(d.name=q),Yt(d)}function vi(d,p,_){p==="number"&&yr(d.ownerDocument)===d||d.defaultValue===""+_||(d.defaultValue=""+_)}function ei(d,p,_,S){if(d=d.options,p){p={};for(var R=0;R<_.length;R++)p["$"+_[R]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),OM=!1;if(ml)try{var Zm={};Object.defineProperty(Zm,"passive",{get:function(){OM=!0}}),window.addEventListener("test",Zm,Zm),window.removeEventListener("test",Zm,Zm)}catch{OM=!1}var Lc=null,LM=null,Rv=null;function NL(){if(Rv)return Rv;var d,p=LM,_=p.length,S,R="value"in Lc?Lc.value:Lc.textContent,O=R.length;for(d=0;d<_&&p[d]===R[d];d++);var q=_-d;for(S=1;S<=q&&p[_-S]===R[O-S];S++);return Rv=R.slice(d,1=eg),LL=" ",IL=!1;function jL(d,p){switch(d){case"keyup":return Bee.indexOf(p.keyCode)!==-1;case"keydown":return p.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function UL(d){return d=d.detail,typeof d=="object"&&"data"in d?d.data:null}var nh=!1;function $ee(d,p){switch(d){case"compositionend":return UL(p);case"keypress":return p.which!==32?null:(IL=!0,LL);case"textInput":return d=p.data,d===LL&&IL?null:d;default:return null}}function Vee(d,p){if(nh)return d==="compositionend"||!zM&&jL(d,p)?(d=NL(),Rv=LM=Lc=null,nh=!1,d):null;switch(d){case"paste":return null;case"keypress":if(!(p.ctrlKey||p.altKey||p.metaKey)||p.ctrlKey&&p.altKey){if(p.char&&1=p)return{node:_,offset:p-d};d=S}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=WL(_)}}function XL(d,p){return d&&p?d===p?!0:d&&d.nodeType===3?!1:p&&p.nodeType===3?XL(d,p.parentNode):"contains"in d?d.contains(p):d.compareDocumentPosition?!!(d.compareDocumentPosition(p)&16):!1:!1}function KL(d){d=d!=null&&d.ownerDocument!=null&&d.ownerDocument.defaultView!=null?d.ownerDocument.defaultView:window;for(var p=yr(d.document);p instanceof d.HTMLIFrameElement;){try{var _=typeof p.contentWindow.location.href=="string"}catch{_=!1}if(_)d=p.contentWindow;else break;p=yr(d.document)}return p}function $M(d){var p=d&&d.nodeName&&d.nodeName.toLowerCase();return p&&(p==="input"&&(d.type==="text"||d.type==="search"||d.type==="tel"||d.type==="url"||d.type==="password")||p==="textarea"||d.contentEditable==="true")}var Qee=ml&&"documentMode"in document&&11>=document.documentMode,rh=null,VM=null,ig=null,GM=!1;function YL(d,p,_){var S=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;GM||rh==null||rh!==yr(S)||(S=rh,"selectionStart"in S&&$M(S)?S={start:S.selectionStart,end:S.selectionEnd}:(S=(S.ownerDocument&&S.ownerDocument.defaultView||window).getSelection(),S={anchorNode:S.anchorNode,anchorOffset:S.anchorOffset,focusNode:S.focusNode,focusOffset:S.focusOffset}),ig&&rg(ig,S)||(ig=S,S=wb(VM,"onSelect"),0>=q,R-=q,Do=1<<32-Be(p)+R|_<en?(vn=Dt,Dt=null):vn=Dt.sibling;var Mn=qe(Oe,Dt,He[en],lt);if(Mn===null){Dt===null&&(Dt=vn);break}d&&Dt&&Mn.alternate===null&&p(Oe,Dt),Ce=O(Mn,Ce,en),wn===null?jt=Mn:wn.sibling=Mn,wn=Mn,Dt=vn}if(en===He.length)return _(Oe,Dt),yn&&xl(Oe,en),jt;if(Dt===null){for(;enen?(vn=Dt,Dt=null):vn=Dt.sibling;var ru=qe(Oe,Dt,Mn.value,lt);if(ru===null){Dt===null&&(Dt=vn);break}d&&Dt&&ru.alternate===null&&p(Oe,Dt),Ce=O(ru,Ce,en),wn===null?jt=ru:wn.sibling=ru,wn=ru,Dt=vn}if(Mn.done)return _(Oe,Dt),yn&&xl(Oe,en),jt;if(Dt===null){for(;!Mn.done;en++,Mn=He.next())Mn=ut(Oe,Mn.value,lt),Mn!==null&&(Ce=O(Mn,Ce,en),wn===null?jt=Mn:wn.sibling=Mn,wn=Mn);return yn&&xl(Oe,en),jt}for(Dt=S(Dt);!Mn.done;en++,Mn=He.next())Mn=rt(Dt,Oe,en,Mn.value,lt),Mn!==null&&(d&&Mn.alternate!==null&&Dt.delete(Mn.key===null?en:Mn.key),Ce=O(Mn,Ce,en),wn===null?jt=Mn:wn.sibling=Mn,wn=Mn);return d&&Dt.forEach(function(vne){return p(Oe,vne)}),yn&&xl(Oe,en),jt}function Fn(Oe,Ce,He,lt){if(typeof He=="object"&&He!==null&&He.type===y&&He.key===null&&(He=He.props.children),typeof He=="object"&&He!==null){switch(He.$$typeof){case m:e:{for(var jt=He.key;Ce!==null;){if(Ce.key===jt){if(jt=He.type,jt===y){if(Ce.tag===7){_(Oe,Ce.sibling),lt=R(Ce,He.props.children),lt.return=Oe,Oe=lt;break e}}else if(Ce.elementType===jt||typeof jt=="object"&&jt!==null&&jt.$$typeof===j&&Md(jt)===Ce.type){_(Oe,Ce.sibling),lt=R(Ce,He.props),ug(lt,He),lt.return=Oe,Oe=lt;break e}_(Oe,Ce);break}else p(Oe,Ce);Ce=Ce.sibling}He.type===y?(lt=bd(He.props.children,Oe.mode,lt,He.key),lt.return=Oe,Oe=lt):(lt=zv(He.type,He.key,He.props,null,Oe.mode,lt),ug(lt,He),lt.return=Oe,Oe=lt)}return q(Oe);case b:e:{for(jt=He.key;Ce!==null;){if(Ce.key===jt)if(Ce.tag===4&&Ce.stateNode.containerInfo===He.containerInfo&&Ce.stateNode.implementation===He.implementation){_(Oe,Ce.sibling),lt=R(Ce,He.children||[]),lt.return=Oe,Oe=lt;break e}else{_(Oe,Ce);break}else p(Oe,Ce);Ce=Ce.sibling}lt=QM(He,Oe.mode,lt),lt.return=Oe,Oe=lt}return q(Oe);case j:return He=Md(He),Fn(Oe,Ce,He,lt)}if($(He))return Rt(Oe,Ce,He,lt);if(I(He)){if(jt=I(He),typeof jt!="function")throw Error(r(150));return He=jt.call(He),Ht(Oe,Ce,He,lt)}if(typeof He.then=="function")return Fn(Oe,Ce,qv(He),lt);if(He.$$typeof===w)return Fn(Oe,Ce,$v(Oe,He),lt);Xv(Oe,He)}return typeof He=="string"&&He!==""||typeof He=="number"||typeof He=="bigint"?(He=""+He,Ce!==null&&Ce.tag===6?(_(Oe,Ce.sibling),lt=R(Ce,He),lt.return=Oe,Oe=lt):(_(Oe,Ce),lt=ZM(He,Oe.mode,lt),lt.return=Oe,Oe=lt),q(Oe)):_(Oe,Ce)}return function(Oe,Ce,He,lt){try{cg=0;var jt=Fn(Oe,Ce,He,lt);return ph=null,jt}catch(Dt){if(Dt===hh||Dt===Gv)throw Dt;var wn=Zs(29,Dt,null,Oe.mode);return wn.lanes=lt,wn.return=Oe,wn}finally{}}}var Ad=bI(!0),yI=bI(!1),zc=!1;function uE(d){d.updateQueue={baseState:d.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function dE(d,p){d=d.updateQueue,p.updateQueue===d&&(p.updateQueue={baseState:d.baseState,firstBaseUpdate:d.firstBaseUpdate,lastBaseUpdate:d.lastBaseUpdate,shared:d.shared,callbacks:null})}function Bc(d){return{lane:d,tag:0,payload:null,callback:null,next:null}}function Hc(d,p,_){var S=d.updateQueue;if(S===null)return null;if(S=S.shared,(Cn&2)!==0){var R=S.pending;return R===null?p.next=p:(p.next=R.next,R.next=p),S.pending=p,p=Fv(d),rI(d,null,_),p}return Uv(d,S,p,_),Fv(d)}function dg(d,p,_){if(p=p.updateQueue,p!==null&&(p=p.shared,(_&4194048)!==0)){var S=p.lanes;S&=d.pendingLanes,_|=S,p.lanes=_,qn(d,_)}}function fE(d,p){var _=d.updateQueue,S=d.alternate;if(S!==null&&(S=S.updateQueue,_===S)){var R=null,O=null;if(_=_.firstBaseUpdate,_!==null){do{var q={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};O===null?R=O=q:O=O.next=q,_=_.next}while(_!==null);O===null?R=O=p:O=O.next=p}else R=O=p;_={baseState:S.baseState,firstBaseUpdate:R,lastBaseUpdate:O,shared:S.shared,callbacks:S.callbacks},d.updateQueue=_;return}d=_.lastBaseUpdate,d===null?_.firstBaseUpdate=p:d.next=p,_.lastBaseUpdate=p}var hE=!1;function fg(){if(hE){var d=fh;if(d!==null)throw d}}function hg(d,p,_,S){hE=!1;var R=d.updateQueue;zc=!1;var O=R.firstBaseUpdate,q=R.lastBaseUpdate,oe=R.shared.pending;if(oe!==null){R.shared.pending=null;var Se=oe,$e=Se.next;Se.next=null,q===null?O=$e:q.next=$e,q=Se;var at=d.alternate;at!==null&&(at=at.updateQueue,oe=at.lastBaseUpdate,oe!==q&&(oe===null?at.firstBaseUpdate=$e:oe.next=$e,at.lastBaseUpdate=Se))}if(O!==null){var ut=R.baseState;q=0,at=$e=Se=null,oe=O;do{var qe=oe.lane&-536870913,rt=qe!==oe.lane;if(rt?(xn&qe)===qe:(S&qe)===qe){qe!==0&&qe===dh&&(hE=!0),at!==null&&(at=at.next={lane:0,tag:oe.tag,payload:oe.payload,callback:null,next:null});e:{var Rt=d,Ht=oe;qe=p;var Fn=_;switch(Ht.tag){case 1:if(Rt=Ht.payload,typeof Rt=="function"){ut=Rt.call(Fn,ut,qe);break e}ut=Rt;break e;case 3:Rt.flags=Rt.flags&-65537|128;case 0:if(Rt=Ht.payload,qe=typeof Rt=="function"?Rt.call(Fn,ut,qe):Rt,qe==null)break e;ut=h({},ut,qe);break e;case 2:zc=!0}}qe=oe.callback,qe!==null&&(d.flags|=64,rt&&(d.flags|=8192),rt=R.callbacks,rt===null?R.callbacks=[qe]:rt.push(qe))}else rt={lane:qe,tag:oe.tag,payload:oe.payload,callback:oe.callback,next:null},at===null?($e=at=rt,Se=ut):at=at.next=rt,q|=qe;if(oe=oe.next,oe===null){if(oe=R.shared.pending,oe===null)break;rt=oe,oe=rt.next,rt.next=null,R.lastBaseUpdate=rt,R.shared.pending=null}}while(!0);at===null&&(Se=ut),R.baseState=Se,R.firstBaseUpdate=$e,R.lastBaseUpdate=at,O===null&&(R.shared.lanes=0),qc|=q,d.lanes=q,d.memoizedState=ut}}function _I(d,p){if(typeof d!="function")throw Error(r(191,d));d.call(p)}function SI(d,p){var _=d.callbacks;if(_!==null)for(d.callbacks=null,d=0;d<_.length;d++)_I(_[d],p)}var mh=H(null),Kv=H(0);function wI(d,p){d=Cl,te(Kv,d),te(mh,p),Cl=d|p.baseLanes}function pE(){te(Kv,Cl),te(mh,mh.current)}function mE(){Cl=Kv.current,Y(mh),Y(Kv)}var Qs=H(null),wa=null;function $c(d){var p=d.alternate;te(Rr,Rr.current&1),te(Qs,d),wa===null&&(p===null||mh.current!==null||p.memoizedState!==null)&&(wa=d)}function gE(d){te(Rr,Rr.current),te(Qs,d),wa===null&&(wa=d)}function MI(d){d.tag===22?(te(Rr,Rr.current),te(Qs,d),wa===null&&(wa=d)):Vc()}function Vc(){te(Rr,Rr.current),te(Qs,Qs.current)}function Js(d){Y(Qs),wa===d&&(wa=null),Y(Rr)}var Rr=H(0);function Yv(d){for(var p=d;p!==null;){if(p.tag===13){var _=p.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||SA(_)||wA(_)))return p}else if(p.tag===19&&(p.memoizedProps.revealOrder==="forwards"||p.memoizedProps.revealOrder==="backwards"||p.memoizedProps.revealOrder==="unstable_legacy-backwards"||p.memoizedProps.revealOrder==="together")){if((p.flags&128)!==0)return p}else if(p.child!==null){p.child.return=p,p=p.child;continue}if(p===d)break;for(;p.sibling===null;){if(p.return===null||p.return===d)return null;p=p.return}p.sibling.return=p.return,p=p.sibling}return null}var yl=0,Jt=null,jn=null,Fr=null,Zv=!1,gh=!1,Td=!1,Qv=0,pg=0,xh=null,lte=0;function _r(){throw Error(r(321))}function xE(d,p){if(p===null)return!1;for(var _=0;_O?O:8;var q=G.T,oe={};G.T=oe,kE(d,!1,p,_);try{var Se=R(),$e=G.S;if($e!==null&&$e(oe,Se),Se!==null&&typeof Se=="object"&&typeof Se.then=="function"){var at=ote(Se,S);gg(d,p,at,na(d))}else gg(d,p,S,na(d))}catch(ut){gg(d,p,{then:function(){},status:"rejected",reason:ut},na())}finally{V.p=O,q!==null&&oe.types!==null&&(q.types=oe.types),G.T=q}}function hte(){}function RE(d,p,_,S){if(d.tag!==5)throw Error(r(476));var R=ej(d).queue;JI(d,R,p,Z,_===null?hte:function(){return tj(d),_(S)})}function ej(d){var p=d.memoizedState;if(p!==null)return p;p={memoizedState:Z,baseState:Z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_l,lastRenderedState:Z},next:null};var _={};return p.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:_l,lastRenderedState:_},next:null},d.memoizedState=p,d=d.alternate,d!==null&&(d.memoizedState=p),p}function tj(d){var p=ej(d);p.next===null&&(p=d.alternate.memoizedState),gg(d,p.next.queue,{},na())}function PE(){return Ii(Dg)}function nj(){return Pr().memoizedState}function rj(){return Pr().memoizedState}function pte(d){for(var p=d.return;p!==null;){switch(p.tag){case 24:case 3:var _=na();d=Bc(_);var S=Hc(p,d,_);S!==null&&(Ds(S,p,_),dg(S,p,_)),p={cache:aE()},d.payload=p;return}p=p.return}}function mte(d,p,_){var S=na();_={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},ib(d)?sj(p,_):(_=KM(d,p,_,S),_!==null&&(Ds(_,d,S),aj(_,p,S)))}function ij(d,p,_){var S=na();gg(d,p,_,S)}function gg(d,p,_,S){var R={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(ib(d))sj(p,R);else{var O=d.alternate;if(d.lanes===0&&(O===null||O.lanes===0)&&(O=p.lastRenderedReducer,O!==null))try{var q=p.lastRenderedState,oe=O(q,_);if(R.hasEagerState=!0,R.eagerState=oe,Ys(oe,q))return Uv(d,p,R,0),Xn===null&&jv(),!1}catch{}finally{}if(_=KM(d,p,R,S),_!==null)return Ds(_,d,S),aj(_,p,S),!0}return!1}function kE(d,p,_,S){if(S={lane:2,revertLane:uA(),gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},ib(d)){if(p)throw Error(r(479))}else p=KM(d,_,S,2),p!==null&&Ds(p,d,2)}function ib(d){var p=d.alternate;return d===Jt||p!==null&&p===Jt}function sj(d,p){gh=Zv=!0;var _=d.pending;_===null?p.next=p:(p.next=_.next,_.next=p),d.pending=p}function aj(d,p,_){if((_&4194048)!==0){var S=p.lanes;S&=d.pendingLanes,_|=S,p.lanes=_,qn(d,_)}}var xg={readContext:Ii,use:eb,useCallback:_r,useContext:_r,useEffect:_r,useImperativeHandle:_r,useLayoutEffect:_r,useInsertionEffect:_r,useMemo:_r,useReducer:_r,useRef:_r,useState:_r,useDebugValue:_r,useDeferredValue:_r,useTransition:_r,useSyncExternalStore:_r,useId:_r,useHostTransitionStatus:_r,useFormState:_r,useActionState:_r,useOptimistic:_r,useMemoCache:_r,useCacheRefresh:_r};xg.useEffectEvent=_r;var oj={readContext:Ii,use:eb,useCallback:function(d,p){return hs().memoizedState=[d,p===void 0?null:p],d},useContext:Ii,useEffect:VI,useImperativeHandle:function(d,p,_){_=_!=null?_.concat([d]):null,nb(4194308,4,XI.bind(null,p,d),_)},useLayoutEffect:function(d,p){return nb(4194308,4,d,p)},useInsertionEffect:function(d,p){nb(4,2,d,p)},useMemo:function(d,p){var _=hs();p=p===void 0?null:p;var S=d();if(Td){Qe(!0);try{d()}finally{Qe(!1)}}return _.memoizedState=[S,p],S},useReducer:function(d,p,_){var S=hs();if(_!==void 0){var R=_(p);if(Td){Qe(!0);try{_(p)}finally{Qe(!1)}}}else R=p;return S.memoizedState=S.baseState=R,d={pending:null,lanes:0,dispatch:null,lastRenderedReducer:d,lastRenderedState:R},S.queue=d,d=d.dispatch=mte.bind(null,Jt,d),[S.memoizedState,d]},useRef:function(d){var p=hs();return d={current:d},p.memoizedState=d},useState:function(d){d=EE(d);var p=d.queue,_=ij.bind(null,Jt,p);return p.dispatch=_,[d.memoizedState,_]},useDebugValue:CE,useDeferredValue:function(d,p){var _=hs();return NE(_,d,p)},useTransition:function(){var d=EE(!1);return d=JI.bind(null,Jt,d.queue,!0,!1),hs().memoizedState=d,[!1,d]},useSyncExternalStore:function(d,p,_){var S=Jt,R=hs();if(yn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=p(),Xn===null)throw Error(r(349));(xn&127)!==0||CI(S,p,_)}R.memoizedState=_;var O={value:_,getSnapshot:p};return R.queue=O,VI(RI.bind(null,S,O,d),[d]),S.flags|=2048,vh(9,{destroy:void 0},NI.bind(null,S,O,_,p),null),_},useId:function(){var d=hs(),p=Xn.identifierPrefix;if(yn){var _=Oo,S=Do;_=(S&~(1<<32-Be(S)-1)).toString(32)+_,p="_"+p+"R_"+_,_=Qv++,0<_&&(p+="H"+_.toString(32)),p+="_"}else _=lte++,p="_"+p+"r_"+_.toString(32)+"_";return d.memoizedState=p},useHostTransitionStatus:PE,useFormState:FI,useActionState:FI,useOptimistic:function(d){var p=hs();p.memoizedState=p.baseState=d;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return p.queue=_,p=kE.bind(null,Jt,!0,_),_.dispatch=p,[d,p]},useMemoCache:SE,useCacheRefresh:function(){return hs().memoizedState=pte.bind(null,Jt)},useEffectEvent:function(d){var p=hs(),_={impl:d};return p.memoizedState=_,function(){if((Cn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},DE={readContext:Ii,use:eb,useCallback:YI,useContext:Ii,useEffect:TE,useImperativeHandle:KI,useInsertionEffect:WI,useLayoutEffect:qI,useMemo:ZI,useReducer:tb,useRef:$I,useState:function(){return tb(_l)},useDebugValue:CE,useDeferredValue:function(d,p){var _=Pr();return QI(_,jn.memoizedState,d,p)},useTransition:function(){var d=tb(_l)[0],p=Pr().memoizedState;return[typeof d=="boolean"?d:mg(d),p]},useSyncExternalStore:TI,useId:nj,useHostTransitionStatus:PE,useFormState:zI,useActionState:zI,useOptimistic:function(d,p){var _=Pr();return DI(_,jn,d,p)},useMemoCache:SE,useCacheRefresh:rj};DE.useEffectEvent=GI;var lj={readContext:Ii,use:eb,useCallback:YI,useContext:Ii,useEffect:TE,useImperativeHandle:KI,useInsertionEffect:WI,useLayoutEffect:qI,useMemo:ZI,useReducer:ME,useRef:$I,useState:function(){return ME(_l)},useDebugValue:CE,useDeferredValue:function(d,p){var _=Pr();return jn===null?NE(_,d,p):QI(_,jn.memoizedState,d,p)},useTransition:function(){var d=ME(_l)[0],p=Pr().memoizedState;return[typeof d=="boolean"?d:mg(d),p]},useSyncExternalStore:TI,useId:nj,useHostTransitionStatus:PE,useFormState:HI,useActionState:HI,useOptimistic:function(d,p){var _=Pr();return jn!==null?DI(_,jn,d,p):(_.baseState=d,[d,_.queue.dispatch])},useMemoCache:SE,useCacheRefresh:rj};lj.useEffectEvent=GI;function OE(d,p,_,S){p=d.memoizedState,_=_(S,p),_=_==null?p:h({},p,_),d.memoizedState=_,d.lanes===0&&(d.updateQueue.baseState=_)}var LE={enqueueSetState:function(d,p,_){d=d._reactInternals;var S=na(),R=Bc(S);R.payload=p,_!=null&&(R.callback=_),p=Hc(d,R,S),p!==null&&(Ds(p,d,S),dg(p,d,S))},enqueueReplaceState:function(d,p,_){d=d._reactInternals;var S=na(),R=Bc(S);R.tag=1,R.payload=p,_!=null&&(R.callback=_),p=Hc(d,R,S),p!==null&&(Ds(p,d,S),dg(p,d,S))},enqueueForceUpdate:function(d,p){d=d._reactInternals;var _=na(),S=Bc(_);S.tag=2,p!=null&&(S.callback=p),p=Hc(d,S,_),p!==null&&(Ds(p,d,_),dg(p,d,_))}};function cj(d,p,_,S,R,O,q){return d=d.stateNode,typeof d.shouldComponentUpdate=="function"?d.shouldComponentUpdate(S,O,q):p.prototype&&p.prototype.isPureReactComponent?!rg(_,S)||!rg(R,O):!0}function uj(d,p,_,S){d=p.state,typeof p.componentWillReceiveProps=="function"&&p.componentWillReceiveProps(_,S),typeof p.UNSAFE_componentWillReceiveProps=="function"&&p.UNSAFE_componentWillReceiveProps(_,S),p.state!==d&&LE.enqueueReplaceState(p,p.state,null)}function Cd(d,p){var _=p;if("ref"in p){_={};for(var S in p)S!=="ref"&&(_[S]=p[S])}if(d=d.defaultProps){_===p&&(_=h({},_));for(var R in d)_[R]===void 0&&(_[R]=d[R])}return _}function dj(d){Iv(d)}function fj(d){console.error(d)}function hj(d){Iv(d)}function sb(d,p){try{var _=d.onUncaughtError;_(p.value,{componentStack:p.stack})}catch(S){setTimeout(function(){throw S})}}function pj(d,p,_){try{var S=d.onCaughtError;S(_.value,{componentStack:_.stack,errorBoundary:p.tag===1?p.stateNode:null})}catch(R){setTimeout(function(){throw R})}}function IE(d,p,_){return _=Bc(_),_.tag=3,_.payload={element:null},_.callback=function(){sb(d,p)},_}function mj(d){return d=Bc(d),d.tag=3,d}function gj(d,p,_,S){var R=_.type.getDerivedStateFromError;if(typeof R=="function"){var O=S.value;d.payload=function(){return R(O)},d.callback=function(){pj(p,_,S)}}var q=_.stateNode;q!==null&&typeof q.componentDidCatch=="function"&&(d.callback=function(){pj(p,_,S),typeof R!="function"&&(Xc===null?Xc=new Set([this]):Xc.add(this));var oe=S.stack;this.componentDidCatch(S.value,{componentStack:oe!==null?oe:""})})}function gte(d,p,_,S,R){if(_.flags|=32768,S!==null&&typeof S=="object"&&typeof S.then=="function"){if(p=_.alternate,p!==null&&uh(p,_,R,!0),_=Qs.current,_!==null){switch(_.tag){case 31:case 13:return wa===null?xb():_.alternate===null&&Sr===0&&(Sr=3),_.flags&=-257,_.flags|=65536,_.lanes=R,S===Wv?_.flags|=16384:(p=_.updateQueue,p===null?_.updateQueue=new Set([S]):p.add(S),oA(d,S,R)),!1;case 22:return _.flags|=65536,S===Wv?_.flags|=16384:(p=_.updateQueue,p===null?(p={transitions:null,markerInstances:null,retryQueue:new Set([S])},_.updateQueue=p):(_=p.retryQueue,_===null?p.retryQueue=new Set([S]):_.add(S)),oA(d,S,R)),!1}throw Error(r(435,_.tag))}return oA(d,S,R),xb(),!1}if(yn)return p=Qs.current,p!==null?((p.flags&65536)===0&&(p.flags|=256),p.flags|=65536,p.lanes=R,S!==tE&&(d=Error(r(422),{cause:S}),ag(ba(d,_)))):(S!==tE&&(p=Error(r(423),{cause:S}),ag(ba(p,_))),d=d.current.alternate,d.flags|=65536,R&=-R,d.lanes|=R,S=ba(S,_),R=IE(d.stateNode,S,R),fE(d,R),Sr!==4&&(Sr=2)),!1;var O=Error(r(520),{cause:S});if(O=ba(O,_),Eg===null?Eg=[O]:Eg.push(O),Sr!==4&&(Sr=2),p===null)return!0;S=ba(S,_),_=p;do{switch(_.tag){case 3:return _.flags|=65536,d=R&-R,_.lanes|=d,d=IE(_.stateNode,S,d),fE(_,d),!1;case 1:if(p=_.type,O=_.stateNode,(_.flags&128)===0&&(typeof p.getDerivedStateFromError=="function"||O!==null&&typeof O.componentDidCatch=="function"&&(Xc===null||!Xc.has(O))))return _.flags|=65536,R&=-R,_.lanes|=R,R=mj(R),gj(R,d,_,S),fE(_,R),!1}_=_.return}while(_!==null);return!1}var jE=Error(r(461)),zr=!1;function ji(d,p,_,S){p.child=d===null?yI(p,null,_,S):Ad(p,d.child,_,S)}function xj(d,p,_,S,R){_=_.render;var O=p.ref;if("ref"in S){var q={};for(var oe in S)oe!=="ref"&&(q[oe]=S[oe])}else q=S;return Sd(p),S=vE(d,p,_,q,O,R),oe=bE(),d!==null&&!zr?(yE(d,p,R),Sl(d,p,R)):(yn&&oe&&JM(p),p.flags|=1,ji(d,p,S,R),p.child)}function vj(d,p,_,S,R){if(d===null){var O=_.type;return typeof O=="function"&&!YM(O)&&O.defaultProps===void 0&&_.compare===null?(p.tag=15,p.type=O,bj(d,p,O,S,R)):(d=zv(_.type,null,S,p,p.mode,R),d.ref=p.ref,d.return=p,p.child=d)}if(O=d.child,!GE(d,R)){var q=O.memoizedProps;if(_=_.compare,_=_!==null?_:rg,_(q,S)&&d.ref===p.ref)return Sl(d,p,R)}return p.flags|=1,d=gl(O,S),d.ref=p.ref,d.return=p,p.child=d}function bj(d,p,_,S,R){if(d!==null){var O=d.memoizedProps;if(rg(O,S)&&d.ref===p.ref)if(zr=!1,p.pendingProps=S=O,GE(d,R))(d.flags&131072)!==0&&(zr=!0);else return p.lanes=d.lanes,Sl(d,p,R)}return UE(d,p,_,S,R)}function yj(d,p,_,S){var R=S.children,O=d!==null?d.memoizedState:null;if(d===null&&p.stateNode===null&&(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),S.mode==="hidden"){if((p.flags&128)!==0){if(O=O!==null?O.baseLanes|_:_,d!==null){for(S=p.child=d.child,R=0;S!==null;)R=R|S.lanes|S.childLanes,S=S.sibling;S=R&~O}else S=0,p.child=null;return _j(d,p,O,_,S)}if((_&536870912)!==0)p.memoizedState={baseLanes:0,cachePool:null},d!==null&&Vv(p,O!==null?O.cachePool:null),O!==null?wI(p,O):pE(),MI(p);else return S=p.lanes=536870912,_j(d,p,O!==null?O.baseLanes|_:_,_,S)}else O!==null?(Vv(p,O.cachePool),wI(p,O),Vc(),p.memoizedState=null):(d!==null&&Vv(p,null),pE(),Vc());return ji(d,p,R,_),p.child}function vg(d,p){return d!==null&&d.tag===22||p.stateNode!==null||(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),p.sibling}function _j(d,p,_,S,R){var O=lE();return O=O===null?null:{parent:Ur._currentValue,pool:O},p.memoizedState={baseLanes:_,cachePool:O},d!==null&&Vv(p,null),pE(),MI(p),d!==null&&uh(d,p,S,!0),p.childLanes=R,null}function ab(d,p){return p=lb({mode:p.mode,children:p.children},d.mode),p.ref=d.ref,d.child=p,p.return=d,p}function Sj(d,p,_){return Ad(p,d.child,null,_),d=ab(p,p.pendingProps),d.flags|=2,Js(p),p.memoizedState=null,d}function xte(d,p,_){var S=p.pendingProps,R=(p.flags&128)!==0;if(p.flags&=-129,d===null){if(yn){if(S.mode==="hidden")return d=ab(p,S),p.lanes=536870912,vg(null,d);if(gE(p),(d=tr)?(d=O4(d,Sa),d=d!==null&&d.data==="&"?d:null,d!==null&&(p.memoizedState={dehydrated:d,treeContext:Ic!==null?{id:Do,overflow:Oo}:null,retryLane:536870912,hydrationErrors:null},_=sI(d),_.return=p,p.child=_,Li=p,tr=null)):d=null,d===null)throw Uc(p);return p.lanes=536870912,null}return ab(p,S)}var O=d.memoizedState;if(O!==null){var q=O.dehydrated;if(gE(p),R)if(p.flags&256)p.flags&=-257,p=Sj(d,p,_);else if(p.memoizedState!==null)p.child=d.child,p.flags|=128,p=null;else throw Error(r(558));else if(zr||uh(d,p,_,!1),R=(_&d.childLanes)!==0,zr||R){if(S=Xn,S!==null&&(q=gi(S,_),q!==0&&q!==O.retryLane))throw O.retryLane=q,vd(d,q),Ds(S,d,q),jE;xb(),p=Sj(d,p,_)}else d=O.treeContext,tr=Ma(q.nextSibling),Li=p,yn=!0,jc=null,Sa=!1,d!==null&&lI(p,d),p=ab(p,S),p.flags|=4096;return p}return d=gl(d.child,{mode:S.mode,children:S.children}),d.ref=p.ref,p.child=d,d.return=p,d}function ob(d,p){var _=p.ref;if(_===null)d!==null&&d.ref!==null&&(p.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(d===null||d.ref!==_)&&(p.flags|=4194816)}}function UE(d,p,_,S,R){return Sd(p),_=vE(d,p,_,S,void 0,R),S=bE(),d!==null&&!zr?(yE(d,p,R),Sl(d,p,R)):(yn&&S&&JM(p),p.flags|=1,ji(d,p,_,R),p.child)}function wj(d,p,_,S,R,O){return Sd(p),p.updateQueue=null,_=AI(p,S,_,R),EI(d),S=bE(),d!==null&&!zr?(yE(d,p,O),Sl(d,p,O)):(yn&&S&&JM(p),p.flags|=1,ji(d,p,_,O),p.child)}function Mj(d,p,_,S,R){if(Sd(p),p.stateNode===null){var O=ah,q=_.contextType;typeof q=="object"&&q!==null&&(O=Ii(q)),O=new _(S,O),p.memoizedState=O.state!==null&&O.state!==void 0?O.state:null,O.updater=LE,p.stateNode=O,O._reactInternals=p,O=p.stateNode,O.props=S,O.state=p.memoizedState,O.refs={},uE(p),q=_.contextType,O.context=typeof q=="object"&&q!==null?Ii(q):ah,O.state=p.memoizedState,q=_.getDerivedStateFromProps,typeof q=="function"&&(OE(p,_,q,S),O.state=p.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof O.getSnapshotBeforeUpdate=="function"||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(q=O.state,typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount(),q!==O.state&&LE.enqueueReplaceState(O,O.state,null),hg(p,S,O,R),fg(),O.state=p.memoizedState),typeof O.componentDidMount=="function"&&(p.flags|=4194308),S=!0}else if(d===null){O=p.stateNode;var oe=p.memoizedProps,Se=Cd(_,oe);O.props=Se;var $e=O.context,at=_.contextType;q=ah,typeof at=="object"&&at!==null&&(q=Ii(at));var ut=_.getDerivedStateFromProps;at=typeof ut=="function"||typeof O.getSnapshotBeforeUpdate=="function",oe=p.pendingProps!==oe,at||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(oe||$e!==q)&&uj(p,O,S,q),zc=!1;var qe=p.memoizedState;O.state=qe,hg(p,S,O,R),fg(),$e=p.memoizedState,oe||qe!==$e||zc?(typeof ut=="function"&&(OE(p,_,ut,S),$e=p.memoizedState),(Se=zc||cj(p,_,Se,S,qe,$e,q))?(at||typeof O.UNSAFE_componentWillMount!="function"&&typeof O.componentWillMount!="function"||(typeof O.componentWillMount=="function"&&O.componentWillMount(),typeof O.UNSAFE_componentWillMount=="function"&&O.UNSAFE_componentWillMount()),typeof O.componentDidMount=="function"&&(p.flags|=4194308)):(typeof O.componentDidMount=="function"&&(p.flags|=4194308),p.memoizedProps=S,p.memoizedState=$e),O.props=S,O.state=$e,O.context=q,S=Se):(typeof O.componentDidMount=="function"&&(p.flags|=4194308),S=!1)}else{O=p.stateNode,dE(d,p),q=p.memoizedProps,at=Cd(_,q),O.props=at,ut=p.pendingProps,qe=O.context,$e=_.contextType,Se=ah,typeof $e=="object"&&$e!==null&&(Se=Ii($e)),oe=_.getDerivedStateFromProps,($e=typeof oe=="function"||typeof O.getSnapshotBeforeUpdate=="function")||typeof O.UNSAFE_componentWillReceiveProps!="function"&&typeof O.componentWillReceiveProps!="function"||(q!==ut||qe!==Se)&&uj(p,O,S,Se),zc=!1,qe=p.memoizedState,O.state=qe,hg(p,S,O,R),fg();var rt=p.memoizedState;q!==ut||qe!==rt||zc||d!==null&&d.dependencies!==null&&Hv(d.dependencies)?(typeof oe=="function"&&(OE(p,_,oe,S),rt=p.memoizedState),(at=zc||cj(p,_,at,S,qe,rt,Se)||d!==null&&d.dependencies!==null&&Hv(d.dependencies))?($e||typeof O.UNSAFE_componentWillUpdate!="function"&&typeof O.componentWillUpdate!="function"||(typeof O.componentWillUpdate=="function"&&O.componentWillUpdate(S,rt,Se),typeof O.UNSAFE_componentWillUpdate=="function"&&O.UNSAFE_componentWillUpdate(S,rt,Se)),typeof O.componentDidUpdate=="function"&&(p.flags|=4),typeof O.getSnapshotBeforeUpdate=="function"&&(p.flags|=1024)):(typeof O.componentDidUpdate!="function"||q===d.memoizedProps&&qe===d.memoizedState||(p.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||q===d.memoizedProps&&qe===d.memoizedState||(p.flags|=1024),p.memoizedProps=S,p.memoizedState=rt),O.props=S,O.state=rt,O.context=Se,S=at):(typeof O.componentDidUpdate!="function"||q===d.memoizedProps&&qe===d.memoizedState||(p.flags|=4),typeof O.getSnapshotBeforeUpdate!="function"||q===d.memoizedProps&&qe===d.memoizedState||(p.flags|=1024),S=!1)}return O=S,ob(d,p),S=(p.flags&128)!==0,O||S?(O=p.stateNode,_=S&&typeof _.getDerivedStateFromError!="function"?null:O.render(),p.flags|=1,d!==null&&S?(p.child=Ad(p,d.child,null,R),p.child=Ad(p,null,_,R)):ji(d,p,_,R),p.memoizedState=O.state,d=p.child):d=Sl(d,p,R),d}function Ej(d,p,_,S){return yd(),p.flags|=256,ji(d,p,_,S),p.child}var FE={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function zE(d){return{baseLanes:d,cachePool:pI()}}function BE(d,p,_){return d=d!==null?d.childLanes&~_:0,p&&(d|=ta),d}function Aj(d,p,_){var S=p.pendingProps,R=!1,O=(p.flags&128)!==0,q;if((q=O)||(q=d!==null&&d.memoizedState===null?!1:(Rr.current&2)!==0),q&&(R=!0,p.flags&=-129),q=(p.flags&32)!==0,p.flags&=-33,d===null){if(yn){if(R?$c(p):Vc(),(d=tr)?(d=O4(d,Sa),d=d!==null&&d.data!=="&"?d:null,d!==null&&(p.memoizedState={dehydrated:d,treeContext:Ic!==null?{id:Do,overflow:Oo}:null,retryLane:536870912,hydrationErrors:null},_=sI(d),_.return=p,p.child=_,Li=p,tr=null)):d=null,d===null)throw Uc(p);return wA(d)?p.lanes=32:p.lanes=536870912,null}var oe=S.children;return S=S.fallback,R?(Vc(),R=p.mode,oe=lb({mode:"hidden",children:oe},R),S=bd(S,R,_,null),oe.return=p,S.return=p,oe.sibling=S,p.child=oe,S=p.child,S.memoizedState=zE(_),S.childLanes=BE(d,q,_),p.memoizedState=FE,vg(null,S)):($c(p),HE(p,oe))}var Se=d.memoizedState;if(Se!==null&&(oe=Se.dehydrated,oe!==null)){if(O)p.flags&256?($c(p),p.flags&=-257,p=$E(d,p,_)):p.memoizedState!==null?(Vc(),p.child=d.child,p.flags|=128,p=null):(Vc(),oe=S.fallback,R=p.mode,S=lb({mode:"visible",children:S.children},R),oe=bd(oe,R,_,null),oe.flags|=2,S.return=p,oe.return=p,S.sibling=oe,p.child=S,Ad(p,d.child,null,_),S=p.child,S.memoizedState=zE(_),S.childLanes=BE(d,q,_),p.memoizedState=FE,p=vg(null,S));else if($c(p),wA(oe)){if(q=oe.nextSibling&&oe.nextSibling.dataset,q)var $e=q.dgst;q=$e,S=Error(r(419)),S.stack="",S.digest=q,ag({value:S,source:null,stack:null}),p=$E(d,p,_)}else if(zr||uh(d,p,_,!1),q=(_&d.childLanes)!==0,zr||q){if(q=Xn,q!==null&&(S=gi(q,_),S!==0&&S!==Se.retryLane))throw Se.retryLane=S,vd(d,S),Ds(q,d,S),jE;SA(oe)||xb(),p=$E(d,p,_)}else SA(oe)?(p.flags|=192,p.child=d.child,p=null):(d=Se.treeContext,tr=Ma(oe.nextSibling),Li=p,yn=!0,jc=null,Sa=!1,d!==null&&lI(p,d),p=HE(p,S.children),p.flags|=4096);return p}return R?(Vc(),oe=S.fallback,R=p.mode,Se=d.child,$e=Se.sibling,S=gl(Se,{mode:"hidden",children:S.children}),S.subtreeFlags=Se.subtreeFlags&65011712,$e!==null?oe=gl($e,oe):(oe=bd(oe,R,_,null),oe.flags|=2),oe.return=p,S.return=p,S.sibling=oe,p.child=S,vg(null,S),S=p.child,oe=d.child.memoizedState,oe===null?oe=zE(_):(R=oe.cachePool,R!==null?(Se=Ur._currentValue,R=R.parent!==Se?{parent:Se,pool:Se}:R):R=pI(),oe={baseLanes:oe.baseLanes|_,cachePool:R}),S.memoizedState=oe,S.childLanes=BE(d,q,_),p.memoizedState=FE,vg(d.child,S)):($c(p),_=d.child,d=_.sibling,_=gl(_,{mode:"visible",children:S.children}),_.return=p,_.sibling=null,d!==null&&(q=p.deletions,q===null?(p.deletions=[d],p.flags|=16):q.push(d)),p.child=_,p.memoizedState=null,_)}function HE(d,p){return p=lb({mode:"visible",children:p},d.mode),p.return=d,d.child=p}function lb(d,p){return d=Zs(22,d,null,p),d.lanes=0,d}function $E(d,p,_){return Ad(p,d.child,null,_),d=HE(p,p.pendingProps.children),d.flags|=2,p.memoizedState=null,d}function Tj(d,p,_){d.lanes|=p;var S=d.alternate;S!==null&&(S.lanes|=p),iE(d.return,p,_)}function VE(d,p,_,S,R,O){var q=d.memoizedState;q===null?d.memoizedState={isBackwards:p,rendering:null,renderingStartTime:0,last:S,tail:_,tailMode:R,treeForkCount:O}:(q.isBackwards=p,q.rendering=null,q.renderingStartTime=0,q.last=S,q.tail=_,q.tailMode=R,q.treeForkCount=O)}function Cj(d,p,_){var S=p.pendingProps,R=S.revealOrder,O=S.tail;S=S.children;var q=Rr.current,oe=(q&2)!==0;if(oe?(q=q&1|2,p.flags|=128):q&=1,te(Rr,q),ji(d,p,S,_),S=yn?sg:0,!oe&&d!==null&&(d.flags&128)!==0)e:for(d=p.child;d!==null;){if(d.tag===13)d.memoizedState!==null&&Tj(d,_,p);else if(d.tag===19)Tj(d,_,p);else if(d.child!==null){d.child.return=d,d=d.child;continue}if(d===p)break e;for(;d.sibling===null;){if(d.return===null||d.return===p)break e;d=d.return}d.sibling.return=d.return,d=d.sibling}switch(R){case"forwards":for(_=p.child,R=null;_!==null;)d=_.alternate,d!==null&&Yv(d)===null&&(R=_),_=_.sibling;_=R,_===null?(R=p.child,p.child=null):(R=_.sibling,_.sibling=null),VE(p,!1,R,_,O,S);break;case"backwards":case"unstable_legacy-backwards":for(_=null,R=p.child,p.child=null;R!==null;){if(d=R.alternate,d!==null&&Yv(d)===null){p.child=R;break}d=R.sibling,R.sibling=_,_=R,R=d}VE(p,!0,_,null,O,S);break;case"together":VE(p,!1,null,null,void 0,S);break;default:p.memoizedState=null}return p.child}function Sl(d,p,_){if(d!==null&&(p.dependencies=d.dependencies),qc|=p.lanes,(_&p.childLanes)===0)if(d!==null){if(uh(d,p,_,!1),(_&p.childLanes)===0)return null}else return null;if(d!==null&&p.child!==d.child)throw Error(r(153));if(p.child!==null){for(d=p.child,_=gl(d,d.pendingProps),p.child=_,_.return=p;d.sibling!==null;)d=d.sibling,_=_.sibling=gl(d,d.pendingProps),_.return=p;_.sibling=null}return p.child}function GE(d,p){return(d.lanes&p)!==0?!0:(d=d.dependencies,!!(d!==null&&Hv(d)))}function vte(d,p,_){switch(p.tag){case 3:Ee(p,p.stateNode.containerInfo),Fc(p,Ur,d.memoizedState.cache),yd();break;case 27:case 5:we(p);break;case 4:Ee(p,p.stateNode.containerInfo);break;case 10:Fc(p,p.type,p.memoizedProps.value);break;case 31:if(p.memoizedState!==null)return p.flags|=128,gE(p),null;break;case 13:var S=p.memoizedState;if(S!==null)return S.dehydrated!==null?($c(p),p.flags|=128,null):(_&p.child.childLanes)!==0?Aj(d,p,_):($c(p),d=Sl(d,p,_),d!==null?d.sibling:null);$c(p);break;case 19:var R=(d.flags&128)!==0;if(S=(_&p.childLanes)!==0,S||(uh(d,p,_,!1),S=(_&p.childLanes)!==0),R){if(S)return Cj(d,p,_);p.flags|=128}if(R=p.memoizedState,R!==null&&(R.rendering=null,R.tail=null,R.lastEffect=null),te(Rr,Rr.current),S)break;return null;case 22:return p.lanes=0,yj(d,p,_,p.pendingProps);case 24:Fc(p,Ur,d.memoizedState.cache)}return Sl(d,p,_)}function Nj(d,p,_){if(d!==null)if(d.memoizedProps!==p.pendingProps)zr=!0;else{if(!GE(d,_)&&(p.flags&128)===0)return zr=!1,vte(d,p,_);zr=(d.flags&131072)!==0}else zr=!1,yn&&(p.flags&1048576)!==0&&oI(p,sg,p.index);switch(p.lanes=0,p.tag){case 16:e:{var S=p.pendingProps;if(d=Md(p.elementType),p.type=d,typeof d=="function")YM(d)?(S=Cd(d,S),p.tag=1,p=Mj(null,p,d,S,_)):(p.tag=0,p=UE(null,p,d,S,_));else{if(d!=null){var R=d.$$typeof;if(R===T){p.tag=11,p=xj(null,p,d,S,_);break e}else if(R===C){p.tag=14,p=vj(null,p,d,S,_);break e}}throw p=D(d)||d,Error(r(306,p,""))}}return p;case 0:return UE(d,p,p.type,p.pendingProps,_);case 1:return S=p.type,R=Cd(S,p.pendingProps),Mj(d,p,S,R,_);case 3:e:{if(Ee(p,p.stateNode.containerInfo),d===null)throw Error(r(387));S=p.pendingProps;var O=p.memoizedState;R=O.element,dE(d,p),hg(p,S,null,_);var q=p.memoizedState;if(S=q.cache,Fc(p,Ur,S),S!==O.cache&&sE(p,[Ur],_,!0),fg(),S=q.element,O.isDehydrated)if(O={element:S,isDehydrated:!1,cache:q.cache},p.updateQueue.baseState=O,p.memoizedState=O,p.flags&256){p=Ej(d,p,S,_);break e}else if(S!==R){R=ba(Error(r(424)),p),ag(R),p=Ej(d,p,S,_);break e}else{switch(d=p.stateNode.containerInfo,d.nodeType){case 9:d=d.body;break;default:d=d.nodeName==="HTML"?d.ownerDocument.body:d}for(tr=Ma(d.firstChild),Li=p,yn=!0,jc=null,Sa=!0,_=yI(p,null,S,_),p.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(yd(),S===R){p=Sl(d,p,_);break e}ji(d,p,S,_)}p=p.child}return p;case 26:return ob(d,p),d===null?(_=z4(p.type,null,p.pendingProps,null))?p.memoizedState=_:yn||(_=p.type,d=p.pendingProps,S=Mb(Q.current).createElement(_),S[pr]=p,S[Cr]=d,Ui(S,_,d),Pe(S),p.stateNode=S):p.memoizedState=z4(p.type,d.memoizedProps,p.pendingProps,d.memoizedState),null;case 27:return we(p),d===null&&yn&&(S=p.stateNode=j4(p.type,p.pendingProps,Q.current),Li=p,Sa=!0,R=tr,Qc(p.type)?(MA=R,tr=Ma(S.firstChild)):tr=R),ji(d,p,p.pendingProps.children,_),ob(d,p),d===null&&(p.flags|=4194304),p.child;case 5:return d===null&&yn&&((R=S=tr)&&(S=Xte(S,p.type,p.pendingProps,Sa),S!==null?(p.stateNode=S,Li=p,tr=Ma(S.firstChild),Sa=!1,R=!0):R=!1),R||Uc(p)),we(p),R=p.type,O=p.pendingProps,q=d!==null?d.memoizedProps:null,S=O.children,bA(R,O)?S=null:q!==null&&bA(R,q)&&(p.flags|=32),p.memoizedState!==null&&(R=vE(d,p,cte,null,null,_),Dg._currentValue=R),ob(d,p),ji(d,p,S,_),p.child;case 6:return d===null&&yn&&((d=_=tr)&&(_=Kte(_,p.pendingProps,Sa),_!==null?(p.stateNode=_,Li=p,tr=null,d=!0):d=!1),d||Uc(p)),null;case 13:return Aj(d,p,_);case 4:return Ee(p,p.stateNode.containerInfo),S=p.pendingProps,d===null?p.child=Ad(p,null,S,_):ji(d,p,S,_),p.child;case 11:return xj(d,p,p.type,p.pendingProps,_);case 7:return ji(d,p,p.pendingProps,_),p.child;case 8:return ji(d,p,p.pendingProps.children,_),p.child;case 12:return ji(d,p,p.pendingProps.children,_),p.child;case 10:return S=p.pendingProps,Fc(p,p.type,S.value),ji(d,p,S.children,_),p.child;case 9:return R=p.type._context,S=p.pendingProps.children,Sd(p),R=Ii(R),S=S(R),p.flags|=1,ji(d,p,S,_),p.child;case 14:return vj(d,p,p.type,p.pendingProps,_);case 15:return bj(d,p,p.type,p.pendingProps,_);case 19:return Cj(d,p,_);case 31:return xte(d,p,_);case 22:return yj(d,p,_,p.pendingProps);case 24:return Sd(p),S=Ii(Ur),d===null?(R=lE(),R===null&&(R=Xn,O=aE(),R.pooledCache=O,O.refCount++,O!==null&&(R.pooledCacheLanes|=_),R=O),p.memoizedState={parent:S,cache:R},uE(p),Fc(p,Ur,R)):((d.lanes&_)!==0&&(dE(d,p),hg(p,null,null,_),fg()),R=d.memoizedState,O=p.memoizedState,R.parent!==S?(R={parent:S,cache:S},p.memoizedState=R,p.lanes===0&&(p.memoizedState=p.updateQueue.baseState=R),Fc(p,Ur,S)):(S=O.cache,Fc(p,Ur,S),S!==R.cache&&sE(p,[Ur],_,!0))),ji(d,p,p.pendingProps.children,_),p.child;case 29:throw p.pendingProps}throw Error(r(156,p.tag))}function wl(d){d.flags|=4}function WE(d,p,_,S,R){if((p=(d.mode&32)!==0)&&(p=!1),p){if(d.flags|=16777216,(R&335544128)===R)if(d.stateNode.complete)d.flags|=8192;else if(n4())d.flags|=8192;else throw Ed=Wv,cE}else d.flags&=-16777217}function Rj(d,p){if(p.type!=="stylesheet"||(p.state.loading&4)!==0)d.flags&=-16777217;else if(d.flags|=16777216,!G4(p))if(n4())d.flags|=8192;else throw Ed=Wv,cE}function cb(d,p){p!==null&&(d.flags|=4),d.flags&16384&&(p=d.tag!==22?yt():536870912,d.lanes|=p,Sh|=p)}function bg(d,p){if(!yn)switch(d.tailMode){case"hidden":p=d.tail;for(var _=null;p!==null;)p.alternate!==null&&(_=p),p=p.sibling;_===null?d.tail=null:_.sibling=null;break;case"collapsed":_=d.tail;for(var S=null;_!==null;)_.alternate!==null&&(S=_),_=_.sibling;S===null?p||d.tail===null?d.tail=null:d.tail.sibling=null:S.sibling=null}}function nr(d){var p=d.alternate!==null&&d.alternate.child===d.child,_=0,S=0;if(p)for(var R=d.child;R!==null;)_|=R.lanes|R.childLanes,S|=R.subtreeFlags&65011712,S|=R.flags&65011712,R.return=d,R=R.sibling;else for(R=d.child;R!==null;)_|=R.lanes|R.childLanes,S|=R.subtreeFlags,S|=R.flags,R.return=d,R=R.sibling;return d.subtreeFlags|=S,d.childLanes=_,p}function bte(d,p,_){var S=p.pendingProps;switch(eE(p),p.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return nr(p),null;case 1:return nr(p),null;case 3:return _=p.stateNode,S=null,d!==null&&(S=d.memoizedState.cache),p.memoizedState.cache!==S&&(p.flags|=2048),bl(Ur),he(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(d===null||d.child===null)&&(ch(p)?wl(p):d===null||d.memoizedState.isDehydrated&&(p.flags&256)===0||(p.flags|=1024,nE())),nr(p),null;case 26:var R=p.type,O=p.memoizedState;return d===null?(wl(p),O!==null?(nr(p),Rj(p,O)):(nr(p),WE(p,R,null,S,_))):O?O!==d.memoizedState?(wl(p),nr(p),Rj(p,O)):(nr(p),p.flags&=-16777217):(d=d.memoizedProps,d!==S&&wl(p),nr(p),WE(p,R,d,S,_)),null;case 27:if(De(p),_=Q.current,R=p.type,d!==null&&p.stateNode!=null)d.memoizedProps!==S&&wl(p);else{if(!S){if(p.stateNode===null)throw Error(r(166));return nr(p),null}d=ae.current,ch(p)?cI(p):(d=j4(R,S,_),p.stateNode=d,wl(p))}return nr(p),null;case 5:if(De(p),R=p.type,d!==null&&p.stateNode!=null)d.memoizedProps!==S&&wl(p);else{if(!S){if(p.stateNode===null)throw Error(r(166));return nr(p),null}if(O=ae.current,ch(p))cI(p);else{var q=Mb(Q.current);switch(O){case 1:O=q.createElementNS("http://www.w3.org/2000/svg",R);break;case 2:O=q.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;default:switch(R){case"svg":O=q.createElementNS("http://www.w3.org/2000/svg",R);break;case"math":O=q.createElementNS("http://www.w3.org/1998/Math/MathML",R);break;case"script":O=q.createElement("div"),O.innerHTML=" - + +