diff --git a/CHANGELOG.md b/CHANGELOG.md
index 698ca2ae1..a0f496a99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,13 +8,21 @@ All notable changes to Bambuddy will be documented in this file.
- **Docker printer discovery** - Subnet scanning for discovering printers when running in Docker with `network_mode: host`. Automatically detects Docker environment and shows subnet input field in Add Printer dialog.
- **Printer model mapping** - Discovery now shows friendly model names (X1C, H2D, P1S) instead of raw SSDP codes (BL-P001, O1D, C11).
- **Discovery API tests** - Comprehensive test coverage for discovery endpoints.
+- **Project filament colors** - Project cards now display filament color swatches from assigned archives.
+- **BOM filter** - Hide completed BOM items with "Hide done" toggle on project detail page.
+- **Projects in backup/restore** - Projects, BOM items, and attachments now included in database backup/restore.
+- **Attachment file validation** - File type validation for project attachments (images, documents, 3D files, archives, scripts, configs).
### Changed
- **GitHub issue template** - Added mandatory printer firmware version field and LAN-only mode checkbox for better bug reports.
- **Docker compose** - Clearer comments explaining `network_mode: host` requirement for printer discovery and camera streaming.
+- **Project card design** - Enhanced visual polish with gradients, shadows, and glow effects on hover.
+- **Project page layout** - Improved spacing and padding on project list and detail pages.
+- **Delete confirmations** - Replaced browser confirm dialogs with styled confirmation modals.
### Fixed
- **Notification module** - Fixed bug where notifications were sent even when printer was offline.
+- **Attachment uploads** - Fixed file attachments not persisting due to SQLAlchemy JSON column mutation detection.
## [0.1.5] - 2025-12-19
diff --git a/README.md b/README.md
index 01fc98d21..f52034b4d 100644
--- a/README.md
+++ b/README.md
@@ -97,14 +97,6 @@
- Print time accuracy stats
- File manager for printer storage
-### 🎛️ Printer Control
-- AMS/AMS-HT temperature & humidity monitoring
-- Chamber temperature & light control
-- Speed profiles & fan controls
-- AI detection modules (spaghetti, first layer)
-- Automated calibration (bed level, vibration)
-- Dual nozzle support
-
diff --git a/backend/app/api/routes/projects.py b/backend/app/api/routes/projects.py
index 0d4337cc6..c43a4a575 100644
--- a/backend/app/api/routes/projects.py
+++ b/backend/app/api/routes/projects.py
@@ -98,7 +98,7 @@ async def compute_project_stats(db: AsyncSession, project_id: int, target_count:
bom_result = await db.execute(
select(
func.count(ProjectBOMItem.id).label("total"),
- func.sum(case((ProjectBOMItem.quantity_printed >= ProjectBOMItem.quantity_needed, 1), else_=0)).label(
+ func.sum(case((ProjectBOMItem.quantity_acquired >= ProjectBOMItem.quantity_needed, 1), else_=0)).label(
"completed"
),
).where(ProjectBOMItem.project_id == project_id)
@@ -183,6 +183,8 @@ async def list_projects(
print_name=a.print_name,
thumbnail_path=a.thumbnail_path,
status=a.status,
+ filament_type=a.filament_type,
+ filament_color=a.filament_color,
)
for a in archives
]
@@ -342,9 +344,11 @@ async def create_project_from_template(
project_id=project.id,
name=item.name,
quantity_needed=item.quantity_needed,
- quantity_printed=0,
+ quantity_acquired=0,
+ unit_price=item.unit_price,
+ sourcing_url=item.sourcing_url,
stl_filename=item.stl_filename,
- notes=item.notes,
+ remarks=item.remarks,
sort_order=item.sort_order,
)
db.add(new_item)
@@ -691,6 +695,63 @@ def get_project_attachments_dir(project_id: int) -> Path:
return base_dir / "projects" / str(project_id) / "attachments"
+# Allowed file extensions for attachments
+ALLOWED_ATTACHMENT_EXTENSIONS = {
+ # Images
+ ".jpg",
+ ".jpeg",
+ ".png",
+ ".gif",
+ ".webp",
+ ".svg",
+ ".bmp",
+ ".ico",
+ # Documents
+ ".pdf",
+ ".doc",
+ ".docx",
+ ".xls",
+ ".xlsx",
+ ".ppt",
+ ".pptx",
+ ".odt",
+ ".ods",
+ ".odp",
+ ".txt",
+ ".rtf",
+ ".csv",
+ ".md",
+ # 3D/CAD files
+ ".stl",
+ ".obj",
+ ".3mf",
+ ".step",
+ ".stp",
+ ".iges",
+ ".igs",
+ ".f3d",
+ ".scad",
+ # Archives
+ ".zip",
+ ".rar",
+ ".7z",
+ ".tar",
+ ".gz",
+ # Code/scripts (for Klipper macros, scripts, etc.)
+ ".py",
+ ".sh",
+ ".cfg",
+ ".conf",
+ ".gcode",
+ ".ini",
+ # Other common formats
+ ".json",
+ ".xml",
+ ".yaml",
+ ".yml",
+}
+
+
@router.post("/{project_id}/attachments")
async def upload_attachment(
project_id: int,
@@ -698,19 +759,28 @@ async def upload_attachment(
db: AsyncSession = Depends(get_db),
):
"""Upload an attachment to a project."""
+ logger.info(f"=== UPLOAD START: {file.filename} for project {project_id} ===")
+
# Verify project exists
result = await db.execute(select(Project).where(Project.id == project_id))
project = result.scalar_one_or_none()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
+ # Validate file extension
+ original_name = file.filename or "unknown"
+ ext = os.path.splitext(original_name)[1].lower()
+ if ext not in ALLOWED_ATTACHMENT_EXTENSIONS:
+ raise HTTPException(
+ status_code=400,
+ detail=f"File type '{ext}' not supported. Allowed: images, PDFs, documents, STL, 3MF, archives.",
+ )
+
# Create attachments directory
attachments_dir = get_project_attachments_dir(project_id)
attachments_dir.mkdir(parents=True, exist_ok=True)
# Generate unique filename
- original_name = file.filename or "unknown"
- ext = os.path.splitext(original_name)[1]
unique_filename = f"{uuid.uuid4().hex}{ext}"
file_path = attachments_dir / unique_filename
@@ -719,30 +789,43 @@ async def upload_attachment(
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
+ logger.info(f"=== FILE SAVED: {file_path}, size: {len(content)} ===")
except Exception as e:
logger.error(f"Failed to save attachment: {e}")
raise HTTPException(status_code=500, detail="Failed to save attachment")
# Update project attachments JSON
- attachments = project.attachments or []
- attachments.append(
- {
- "filename": unique_filename,
- "original_name": original_name,
- "size": len(content),
- "uploaded_at": datetime.now().isoformat(),
- }
- )
+ attachments = list(project.attachments or [])
+ new_attachment = {
+ "filename": unique_filename,
+ "original_name": original_name,
+ "size": len(content),
+ "uploaded_at": datetime.now().isoformat(),
+ }
+ attachments.append(new_attachment)
+
+ # Simple ORM update
project.attachments = attachments
+ db.add(project) # Explicitly add to session
+
+ logger.info(f"=== BEFORE COMMIT: {len(attachments)} attachments ===")
await db.flush()
- await db.refresh(project)
+ await db.commit()
+
+ logger.info("=== AFTER COMMIT ===")
+
+ # Verify by re-querying
+ result = await db.execute(select(Project).where(Project.id == project_id))
+ fresh_project = result.scalar_one()
+
+ logger.info(f"=== VERIFIED: {len(fresh_project.attachments or [])} attachments ===")
return {
"status": "success",
"filename": unique_filename,
"original_name": original_name,
- "attachments": project.attachments,
+ "attachments": fresh_project.attachments,
}
@@ -854,13 +937,15 @@ async def list_bom_items(
project_id=item.project_id,
name=item.name,
quantity_needed=item.quantity_needed,
- quantity_printed=item.quantity_printed,
+ quantity_acquired=item.quantity_acquired,
+ unit_price=item.unit_price,
+ sourcing_url=item.sourcing_url,
archive_id=item.archive_id,
archive_name=archive_name,
stl_filename=item.stl_filename,
- notes=item.notes,
+ remarks=item.remarks,
sort_order=item.sort_order,
- is_complete=item.quantity_printed >= item.quantity_needed,
+ is_complete=item.quantity_acquired >= item.quantity_needed,
created_at=item.created_at,
updated_at=item.updated_at,
)
@@ -891,9 +976,11 @@ async def create_bom_item(
project_id=project_id,
name=data.name,
quantity_needed=data.quantity_needed,
+ unit_price=data.unit_price,
+ sourcing_url=data.sourcing_url,
archive_id=data.archive_id,
stl_filename=data.stl_filename,
- notes=data.notes,
+ remarks=data.remarks,
sort_order=max_order + 1,
)
db.add(item)
@@ -911,13 +998,15 @@ async def create_bom_item(
project_id=item.project_id,
name=item.name,
quantity_needed=item.quantity_needed,
- quantity_printed=item.quantity_printed,
+ quantity_acquired=item.quantity_acquired,
+ unit_price=item.unit_price,
+ sourcing_url=item.sourcing_url,
archive_id=item.archive_id,
archive_name=archive_name,
stl_filename=item.stl_filename,
- notes=item.notes,
+ remarks=item.remarks,
sort_order=item.sort_order,
- is_complete=item.quantity_printed >= item.quantity_needed,
+ is_complete=item.quantity_acquired >= item.quantity_needed,
created_at=item.created_at,
updated_at=item.updated_at,
)
@@ -946,14 +1035,18 @@ async def update_bom_item(
item.name = data.name
if data.quantity_needed is not None:
item.quantity_needed = data.quantity_needed
- if data.quantity_printed is not None:
- item.quantity_printed = data.quantity_printed
+ if data.quantity_acquired is not None:
+ item.quantity_acquired = data.quantity_acquired
+ if data.unit_price is not None:
+ item.unit_price = data.unit_price if data.unit_price != 0 else None
+ if data.sourcing_url is not None:
+ item.sourcing_url = data.sourcing_url if data.sourcing_url else None
if data.archive_id is not None:
item.archive_id = data.archive_id if data.archive_id != 0 else None
if data.stl_filename is not None:
item.stl_filename = data.stl_filename if data.stl_filename else None
- if data.notes is not None:
- item.notes = data.notes if data.notes else None
+ if data.remarks is not None:
+ item.remarks = data.remarks if data.remarks else None
await db.flush()
await db.refresh(item)
@@ -969,13 +1062,15 @@ async def update_bom_item(
project_id=item.project_id,
name=item.name,
quantity_needed=item.quantity_needed,
- quantity_printed=item.quantity_printed,
+ quantity_acquired=item.quantity_acquired,
+ unit_price=item.unit_price,
+ sourcing_url=item.sourcing_url,
archive_id=item.archive_id,
archive_name=archive_name,
stl_filename=item.stl_filename,
- notes=item.notes,
+ remarks=item.remarks,
sort_order=item.sort_order,
- is_complete=item.quantity_printed >= item.quantity_needed,
+ is_complete=item.quantity_acquired >= item.quantity_needed,
created_at=item.created_at,
updated_at=item.updated_at,
)
@@ -1041,9 +1136,11 @@ async def create_template_from_project(
project_id=template.id,
name=item.name,
quantity_needed=item.quantity_needed,
- quantity_printed=0,
+ quantity_acquired=0,
+ unit_price=item.unit_price,
+ sourcing_url=item.sourcing_url,
stl_filename=item.stl_filename,
- notes=item.notes,
+ remarks=item.remarks,
sort_order=item.sort_order,
)
db.add(new_item)
diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py
index 64eb2766e..2f29a323f 100644
--- a/backend/app/api/routes/settings.py
+++ b/backend/app/api/routes/settings.py
@@ -3,28 +3,28 @@ import json
import zipfile
from datetime import datetime
from pathlib import Path
-from typing import Optional
-from fastapi import APIRouter, Depends, UploadFile, File, Query
+from fastapi import APIRouter, Depends, File, Query, UploadFile
from fastapi.responses import JSONResponse, StreamingResponse
-from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
+from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.core.config import settings as app_settings
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.notification_template import NotificationTemplate
-from backend.app.models.smart_plug import SmartPlug
-from backend.app.models.printer import Printer
-from backend.app.models.filament import Filament
-from backend.app.models.maintenance import MaintenanceType, PrinterMaintenance, MaintenanceHistory
from backend.app.models.archive import PrintArchive
from backend.app.models.external_link import ExternalLink
+from backend.app.models.filament import Filament
+from backend.app.models.maintenance import MaintenanceType
+from backend.app.models.notification import NotificationProvider
+from backend.app.models.notification_template import NotificationTemplate
+from backend.app.models.printer import Printer
+from backend.app.models.project import Project
+from backend.app.models.project_bom import ProjectBOMItem
+from backend.app.models.settings import Settings
+from backend.app.models.smart_plug import SmartPlug
from backend.app.schemas.settings import AppSettings, AppSettingsUpdate
from backend.app.services.printer_manager import printer_manager
-from backend.app.services.spoolman import init_spoolman_client, get_spoolman_client
-
+from backend.app.services.spoolman import init_spoolman_client
router = APIRouter(prefix="/settings", tags=["settings"])
@@ -63,7 +63,14 @@ async def get_settings(db: AsyncSession = Depends(get_db)):
for setting in db_settings:
if setting.key in settings_dict:
# Parse the value based on the expected type
- if setting.key in ["auto_archive", "save_thumbnails", "capture_finish_photo", "spoolman_enabled", "check_updates", "telemetry_enabled"]:
+ if setting.key in [
+ "auto_archive",
+ "save_thumbnails",
+ "capture_finish_photo",
+ "spoolman_enabled",
+ "check_updates",
+ "telemetry_enabled",
+ ]:
settings_dict[setting.key] = setting.value.lower() == "true"
elif setting.key in ["default_filament_cost", "energy_cost_per_kwh", "ams_temp_good", "ams_temp_fair"]:
settings_dict[setting.key] = float(setting.value)
@@ -173,6 +180,7 @@ async def export_backup(
include_filaments: bool = Query(False, description="Include filament inventory"),
include_maintenance: bool = Query(False, description="Include maintenance types and records"),
include_archives: bool = Query(False, description="Include print archive metadata"),
+ include_projects: bool = Query(False, description="Include projects with BOM items"),
include_access_codes: bool = Query(False, description="Include printer access codes (security risk!)"),
):
"""Export selected data as JSON backup."""
@@ -195,27 +203,29 @@ async def export_backup(
providers = result.scalars().all()
backup["notification_providers"] = []
for p in providers:
- backup["notification_providers"].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,
- "daily_digest_enabled": getattr(p, 'daily_digest_enabled', False),
- "daily_digest_time": getattr(p, 'daily_digest_time', None),
- "printer_id": getattr(p, 'printer_id', None),
- })
+ backup["notification_providers"].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,
+ "daily_digest_enabled": getattr(p, "daily_digest_enabled", False),
+ "daily_digest_time": getattr(p, "daily_digest_time", None),
+ "printer_id": getattr(p, "printer_id", None),
+ }
+ )
backup["included"].append("notification_providers")
# Notification templates
@@ -224,13 +234,15 @@ async def export_backup(
templates = result.scalars().all()
backup["notification_templates"] = []
for t in templates:
- backup["notification_templates"].append({
- "event_type": t.event_type,
- "name": t.name,
- "title_template": t.title_template,
- "body_template": t.body_template,
- "is_default": t.is_default,
- })
+ backup["notification_templates"].append(
+ {
+ "event_type": t.event_type,
+ "name": t.name,
+ "title_template": t.title_template,
+ "body_template": t.body_template,
+ "is_default": t.is_default,
+ }
+ )
backup["included"].append("notification_templates")
# Smart plugs
@@ -239,25 +251,27 @@ async def export_backup(
plugs = result.scalars().all()
backup["smart_plugs"] = []
for plug in plugs:
- backup["smart_plugs"].append({
- "name": plug.name,
- "ip_address": plug.ip_address,
- "printer_id": plug.printer_id,
- "enabled": plug.enabled,
- "auto_on": plug.auto_on,
- "auto_off": plug.auto_off,
- "off_delay_mode": plug.off_delay_mode,
- "off_delay_minutes": plug.off_delay_minutes,
- "off_temp_threshold": plug.off_temp_threshold,
- "username": plug.username,
- "password": plug.password,
- "power_alert_enabled": plug.power_alert_enabled,
- "power_alert_high": plug.power_alert_high,
- "power_alert_low": plug.power_alert_low,
- "schedule_enabled": plug.schedule_enabled,
- "schedule_on_time": plug.schedule_on_time,
- "schedule_off_time": plug.schedule_off_time,
- })
+ backup["smart_plugs"].append(
+ {
+ "name": plug.name,
+ "ip_address": plug.ip_address,
+ "printer_id": plug.printer_id,
+ "enabled": plug.enabled,
+ "auto_on": plug.auto_on,
+ "auto_off": plug.auto_off,
+ "off_delay_mode": plug.off_delay_mode,
+ "off_delay_minutes": plug.off_delay_minutes,
+ "off_temp_threshold": plug.off_temp_threshold,
+ "username": plug.username,
+ "password": plug.password,
+ "power_alert_enabled": plug.power_alert_enabled,
+ "power_alert_high": plug.power_alert_high,
+ "power_alert_low": plug.power_alert_low,
+ "schedule_enabled": plug.schedule_enabled,
+ "schedule_on_time": plug.schedule_on_time,
+ "schedule_off_time": plug.schedule_off_time,
+ }
+ )
backup["included"].append("smart_plugs")
# External links
@@ -312,21 +326,23 @@ async def export_backup(
filaments = result.scalars().all()
backup["filaments"] = []
for f in filaments:
- backup["filaments"].append({
- "name": f.name,
- "type": f.type,
- "brand": f.brand,
- "color": f.color,
- "color_hex": f.color_hex,
- "cost_per_kg": f.cost_per_kg,
- "spool_weight_g": f.spool_weight_g,
- "currency": f.currency,
- "density": f.density,
- "print_temp_min": f.print_temp_min,
- "print_temp_max": f.print_temp_max,
- "bed_temp_min": f.bed_temp_min,
- "bed_temp_max": f.bed_temp_max,
- })
+ backup["filaments"].append(
+ {
+ "name": f.name,
+ "type": f.type,
+ "brand": f.brand,
+ "color": f.color,
+ "color_hex": f.color_hex,
+ "cost_per_kg": f.cost_per_kg,
+ "spool_weight_g": f.spool_weight_g,
+ "currency": f.currency,
+ "density": f.density,
+ "print_temp_min": f.print_temp_min,
+ "print_temp_max": f.print_temp_max,
+ "bed_temp_min": f.bed_temp_min,
+ "bed_temp_max": f.bed_temp_max,
+ }
+ )
backup["included"].append("filaments")
# Maintenance types and records
@@ -336,14 +352,16 @@ async def export_backup(
types = result.scalars().all()
backup["maintenance_types"] = []
for mt in types:
- backup["maintenance_types"].append({
- "name": mt.name,
- "description": mt.description,
- "default_interval_hours": mt.default_interval_hours,
- "interval_type": mt.interval_type,
- "icon": mt.icon,
- "is_system": mt.is_system,
- })
+ backup["maintenance_types"].append(
+ {
+ "name": mt.name,
+ "description": mt.description,
+ "default_interval_hours": mt.default_interval_hours,
+ "interval_type": mt.interval_type,
+ "icon": mt.icon,
+ "is_system": mt.is_system,
+ }
+ )
backup["included"].append("maintenance_types")
# Collect files for ZIP (icons + archives)
@@ -365,9 +383,17 @@ async def export_backup(
backup["archives"] = []
base_dir = app_settings.base_dir
+ # Build project ID to name mapping for archive export
+ project_id_to_name: dict[int, str] = {}
+ if include_projects:
+ proj_result = await db.execute(select(Project))
+ for proj in proj_result.scalars().all():
+ project_id_to_name[proj.id] = proj.name
+
for a in archives:
archive_data = {
"filename": a.filename,
+ "project_name": project_id_to_name.get(a.project_id) if a.project_id else None,
"file_size": a.file_size,
"content_hash": a.content_hash,
"print_name": a.print_name,
@@ -432,10 +458,61 @@ async def export_backup(
backup["archives"].append(archive_data)
backup["included"].append("archives")
+ # Projects with BOM items
+ if include_projects:
+ result = await db.execute(select(Project))
+ projects = result.scalars().all()
+ backup["projects"] = []
+
+ for p in projects:
+ # Get BOM items for this project
+ bom_result = await db.execute(select(ProjectBOMItem).where(ProjectBOMItem.project_id == p.id))
+ bom_items = bom_result.scalars().all()
+
+ project_data = {
+ "name": p.name,
+ "description": p.description,
+ "color": p.color,
+ "status": p.status,
+ "target_count": p.target_count,
+ "notes": p.notes,
+ "tags": p.tags,
+ "due_date": p.due_date.isoformat() if p.due_date else None,
+ "priority": p.priority,
+ "budget": p.budget,
+ "is_template": p.is_template,
+ "bom_items": [
+ {
+ "name": item.name,
+ "quantity_needed": item.quantity_needed,
+ "quantity_acquired": item.quantity_acquired,
+ "unit_price": item.unit_price,
+ "sourcing_url": item.sourcing_url,
+ "stl_filename": item.stl_filename,
+ "remarks": item.remarks,
+ "sort_order": item.sort_order,
+ }
+ for item in bom_items
+ ],
+ }
+
+ # Include attachment files for ZIP
+ if p.attachments:
+ project_data["attachments"] = p.attachments
+ attachments_dir = base_dir / "projects" / str(p.id) / "attachments"
+ for att in p.attachments:
+ att_path = attachments_dir / att.get("filename", "")
+ if att_path.exists():
+ zip_path = f"projects/{p.id}/attachments/{att['filename']}"
+ backup_files.append((zip_path, att_path))
+
+ backup["projects"].append(project_data)
+ backup["included"].append("projects")
+
# If there are files to include (icons or archives), create ZIP file
if backup_files:
zip_buffer = io.BytesIO()
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
# Add backup.json
zf.writestr("backup.json", json.dumps(backup, indent=2))
@@ -454,7 +531,7 @@ async def export_backup(
return StreamingResponse(
zip_buffer,
media_type="application/zip",
- headers={"Content-Disposition": f"attachment; filename={filename}"}
+ headers={"Content-Disposition": f"attachment; filename={filename}"},
)
# Otherwise return JSON
@@ -462,7 +539,7 @@ async def export_backup(
content=backup,
headers={
"Content-Disposition": f"attachment; filename=bambuddy-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
- }
+ },
)
@@ -479,27 +556,27 @@ async def import_backup(
files_restored = 0
# Check if it's a ZIP file
- if file.filename and file.filename.endswith('.zip'):
+ if file.filename and file.filename.endswith(".zip"):
try:
zip_buffer = io.BytesIO(content)
- with zipfile.ZipFile(zip_buffer, 'r') as zf:
+ with zipfile.ZipFile(zip_buffer, "r") as zf:
# Extract backup.json
- if 'backup.json' not in zf.namelist():
+ if "backup.json" not in zf.namelist():
return {"success": False, "message": "Invalid ZIP: missing backup.json"}
- backup_content = zf.read('backup.json')
+ backup_content = zf.read("backup.json")
backup = json.loads(backup_content.decode("utf-8"))
# Extract all other files to base_dir
for zip_path in zf.namelist():
- if zip_path == 'backup.json':
+ if zip_path == "backup.json":
continue
# Ensure path is safe (no path traversal)
- if '..' in zip_path or zip_path.startswith('/'):
+ if ".." in zip_path or zip_path.startswith("/"):
continue
target_path = base_dir / zip_path
target_path.parent.mkdir(parents=True, exist_ok=True)
- with zf.open(zip_path) as src, open(target_path, 'wb') as dst:
+ with zf.open(zip_path) as src, open(target_path, "wb") as dst:
dst.write(src.read())
files_restored += 1
except zipfile.BadZipFile:
@@ -520,6 +597,7 @@ async def import_backup(
"printers": 0,
"filaments": 0,
"maintenance_types": 0,
+ "projects": 0,
}
skipped = {
"settings": 0,
@@ -531,6 +609,7 @@ async def import_backup(
"filaments": 0,
"maintenance_types": 0,
"archives": 0,
+ "projects": 0,
}
skipped_details = {
"notification_providers": [],
@@ -540,6 +619,7 @@ async def import_backup(
"filaments": [],
"maintenance_types": [],
"archives": [],
+ "projects": [],
}
# Restore settings (always overwrites)
@@ -616,9 +696,7 @@ async def import_backup(
if "notification_templates" in backup:
for template_data in backup["notification_templates"]:
result = await db.execute(
- select(NotificationTemplate).where(
- NotificationTemplate.event_type == template_data["event_type"]
- )
+ select(NotificationTemplate).where(NotificationTemplate.event_type == template_data["event_type"])
)
existing = result.scalar_one_or_none()
if existing:
@@ -641,9 +719,7 @@ async def import_backup(
# Restore smart plugs (skip or overwrite duplicates by IP)
if "smart_plugs" in backup:
for plug_data in backup["smart_plugs"]:
- result = await db.execute(
- select(SmartPlug).where(SmartPlug.ip_address == plug_data["ip_address"])
- )
+ result = await db.execute(select(SmartPlug).where(SmartPlug.ip_address == plug_data["ip_address"]))
existing = result.scalar_one_or_none()
if existing:
if overwrite:
@@ -697,10 +773,7 @@ async def import_backup(
for link_data in backup["external_links"]:
result = await db.execute(
- select(ExternalLink).where(
- ExternalLink.name == link_data["name"],
- ExternalLink.url == link_data["url"]
- )
+ select(ExternalLink).where(ExternalLink.name == link_data["name"], ExternalLink.url == link_data["url"])
)
existing = result.scalar_one_or_none()
if existing:
@@ -728,9 +801,7 @@ async def import_backup(
# Restore printers (skip or overwrite duplicates by serial_number)
if "printers" in backup:
for printer_data in backup["printers"]:
- result = await db.execute(
- select(Printer).where(Printer.serial_number == printer_data["serial_number"])
- )
+ result = await db.execute(select(Printer).where(Printer.serial_number == printer_data["serial_number"]))
existing = result.scalar_one_or_none()
if existing:
if overwrite:
@@ -805,7 +876,9 @@ async def import_backup(
restored["filaments"] += 1
else:
skipped["filaments"] += 1
- skipped_details["filaments"].append(f"{filament_data.get('brand', '')} {filament_data['name']} ({filament_data['type']})")
+ skipped_details["filaments"].append(
+ f"{filament_data.get('brand', '')} {filament_data['name']} ({filament_data['type']})"
+ )
else:
filament = Filament(
name=filament_data["name"],
@@ -828,9 +901,7 @@ async def import_backup(
# Restore maintenance types (skip or overwrite duplicates by name)
if "maintenance_types" in backup:
for mt_data in backup["maintenance_types"]:
- result = await db.execute(
- select(MaintenanceType).where(MaintenanceType.name == mt_data["name"])
- )
+ result = await db.execute(select(MaintenanceType).where(MaintenanceType.name == mt_data["name"]))
existing = result.scalar_one_or_none()
if existing:
if overwrite:
@@ -861,9 +932,7 @@ async def import_backup(
# Skip if no content_hash or already exists
content_hash = archive_data.get("content_hash")
if content_hash:
- result = await db.execute(
- select(PrintArchive).where(PrintArchive.content_hash == content_hash)
- )
+ result = await db.execute(select(PrintArchive).where(PrintArchive.content_hash == content_hash))
existing = result.scalar_one_or_none()
if existing:
skipped["archives"] += 1
@@ -907,15 +976,111 @@ async def import_backup(
db.add(archive)
restored["archives"] = restored.get("archives", 0) + 1
+ # Restore projects (skip or overwrite duplicates by name)
+ if "projects" in backup:
+ for project_data in backup["projects"]:
+ result = await db.execute(select(Project).where(Project.name == project_data["name"]))
+ existing = result.scalar_one_or_none()
+ if existing:
+ if overwrite:
+ # Update existing project
+ existing.description = project_data.get("description")
+ existing.color = project_data.get("color")
+ existing.status = project_data.get("status", "active")
+ existing.target_count = project_data.get("target_count")
+ existing.notes = project_data.get("notes")
+ existing.tags = project_data.get("tags")
+ existing.priority = project_data.get("priority", "normal")
+ existing.budget = project_data.get("budget")
+ existing.is_template = project_data.get("is_template", False)
+ existing.attachments = project_data.get("attachments")
+ if project_data.get("due_date"):
+ existing.due_date = datetime.fromisoformat(project_data["due_date"])
+
+ # Delete existing BOM items and re-add
+ await db.execute(ProjectBOMItem.__table__.delete().where(ProjectBOMItem.project_id == existing.id))
+ for bom_data in project_data.get("bom_items", []):
+ bom_item = ProjectBOMItem(
+ project_id=existing.id,
+ name=bom_data["name"],
+ quantity_needed=bom_data.get("quantity_needed", 1),
+ quantity_acquired=bom_data.get("quantity_acquired", 0),
+ unit_price=bom_data.get("unit_price"),
+ sourcing_url=bom_data.get("sourcing_url"),
+ stl_filename=bom_data.get("stl_filename"),
+ remarks=bom_data.get("remarks"),
+ sort_order=bom_data.get("sort_order", 0),
+ )
+ db.add(bom_item)
+
+ restored["projects"] += 1
+ else:
+ skipped["projects"] += 1
+ skipped_details["projects"].append(project_data["name"])
+ else:
+ # Create new project
+ project = Project(
+ name=project_data["name"],
+ description=project_data.get("description"),
+ color=project_data.get("color"),
+ status=project_data.get("status", "active"),
+ target_count=project_data.get("target_count"),
+ notes=project_data.get("notes"),
+ tags=project_data.get("tags"),
+ priority=project_data.get("priority", "normal"),
+ budget=project_data.get("budget"),
+ is_template=project_data.get("is_template", False),
+ attachments=project_data.get("attachments"),
+ )
+ if project_data.get("due_date"):
+ project.due_date = datetime.fromisoformat(project_data["due_date"])
+
+ db.add(project)
+ await db.flush() # Get the project ID
+
+ # Add BOM items
+ for bom_data in project_data.get("bom_items", []):
+ bom_item = ProjectBOMItem(
+ project_id=project.id,
+ name=bom_data["name"],
+ quantity_needed=bom_data.get("quantity_needed", 1),
+ quantity_acquired=bom_data.get("quantity_acquired", 0),
+ unit_price=bom_data.get("unit_price"),
+ sourcing_url=bom_data.get("sourcing_url"),
+ stl_filename=bom_data.get("stl_filename"),
+ remarks=bom_data.get("remarks"),
+ sort_order=bom_data.get("sort_order", 0),
+ )
+ db.add(bom_item)
+
+ restored["projects"] += 1
+
+ # Link archives to projects by name (after both are restored)
+ if "archives" in backup and "projects" in backup:
+ # Build project name to ID mapping
+ proj_result = await db.execute(select(Project))
+ project_name_to_id: dict[str, int] = {}
+ for proj in proj_result.scalars().all():
+ project_name_to_id[proj.name] = proj.id
+
+ # Update archives with project_id
+ for archive_data in backup["archives"]:
+ project_name = archive_data.get("project_name")
+ if project_name and project_name in project_name_to_id:
+ content_hash = archive_data.get("content_hash")
+ if content_hash:
+ result = await db.execute(select(PrintArchive).where(PrintArchive.content_hash == content_hash))
+ archive = result.scalar_one_or_none()
+ if archive:
+ archive.project_id = project_name_to_id[project_name]
+
await db.commit()
# If printers were in the backup (restored, updated, or skipped), reconnect all active printers
# This ensures connections are re-established after restore, even if printers were skipped
if "printers" in backup:
# Need fresh query after commit to get proper IDs for newly created printers
- result = await db.execute(
- select(Printer).where(Printer.is_active == True)
- )
+ result = await db.execute(select(Printer).where(Printer.is_active.is_(True)))
active_printers = result.scalars().all()
for printer in active_printers:
# This will disconnect existing connection (if any) and reconnect
diff --git a/backend/app/core/database.py b/backend/app/core/database.py
index 63a5b6054..d1a3c74a0 100644
--- a/backend/app/core/database.py
+++ b/backend/app/core/database.py
@@ -335,6 +335,30 @@ async def run_migrations(conn):
except Exception:
pass
+ # Migration: Rename quantity_printed to quantity_acquired in project_bom_items
+ try:
+ await conn.execute(text("ALTER TABLE project_bom_items RENAME COLUMN quantity_printed TO quantity_acquired"))
+ except Exception:
+ pass
+
+ # Migration: Add unit_price column to project_bom_items
+ try:
+ await conn.execute(text("ALTER TABLE project_bom_items ADD COLUMN unit_price REAL"))
+ except Exception:
+ pass
+
+ # Migration: Add sourcing_url column to project_bom_items
+ try:
+ await conn.execute(text("ALTER TABLE project_bom_items ADD COLUMN sourcing_url VARCHAR(512)"))
+ except Exception:
+ pass
+
+ # Migration: Rename notes to remarks in project_bom_items
+ try:
+ await conn.execute(text("ALTER TABLE project_bom_items RENAME COLUMN notes TO remarks"))
+ except Exception:
+ pass
+
async def seed_notification_templates():
"""Seed default notification templates if they don't exist."""
diff --git a/backend/app/models/project_bom.py b/backend/app/models/project_bom.py
index 89fdd58c5..c3fb8a879 100644
--- a/backend/app/models/project_bom.py
+++ b/backend/app/models/project_bom.py
@@ -1,13 +1,17 @@
from datetime import datetime
-from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
+from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.core.database import Base
class ProjectBOMItem(Base):
- """Bill of Materials item for a project."""
+ """Bill of Materials item for a project.
+
+ Tracks sourced/purchased parts (hardware, electronics, screws, etc.)
+ that need to be acquired for a project.
+ """
__tablename__ = "project_bom_items"
@@ -15,16 +19,20 @@ class ProjectBOMItem(Base):
project_id: Mapped[int] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
name: Mapped[str] = mapped_column(String(255))
quantity_needed: Mapped[int] = mapped_column(Integer, default=1)
- quantity_printed: Mapped[int] = mapped_column(Integer, default=0)
+ quantity_acquired: Mapped[int] = mapped_column(Integer, default=0)
- # Optional link to archive that prints this part
+ # Sourcing information
+ unit_price: Mapped[float | None] = mapped_column(Float, nullable=True)
+ sourcing_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
+
+ # Optional link to archive (for reference)
archive_id: Mapped[int | None] = mapped_column(ForeignKey("print_archives.id", ondelete="SET NULL"), nullable=True)
- # Reference to attachment filename (STL file)
+ # Reference to attachment filename
stl_filename: Mapped[str | None] = mapped_column(String(255), nullable=True)
- # Notes about this part
- notes: Mapped[str | None] = mapped_column(Text, nullable=True)
+ # Remarks about this part
+ remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
# Sort order
sort_order: Mapped[int] = mapped_column(Integer, default=0)
diff --git a/backend/app/schemas/project.py b/backend/app/schemas/project.py
index a7b25be04..2fa8a5c14 100644
--- a/backend/app/schemas/project.py
+++ b/backend/app/schemas/project.py
@@ -100,6 +100,8 @@ class ArchivePreview(BaseModel):
print_name: str | None
thumbnail_path: str | None
status: str
+ filament_type: str | None = None
+ filament_color: str | None = None
class ProjectListResponse(BaseModel):
@@ -135,15 +137,17 @@ class BatchAddQueueItems(BaseModel):
queue_item_ids: list[int]
-# Phase 7: BOM Schemas
+# Phase 7: BOM Schemas - Tracks sourced/purchased parts
class BOMItemCreate(BaseModel):
"""Schema for creating a BOM item."""
name: str
quantity_needed: int = 1
+ unit_price: float | None = None
+ sourcing_url: str | None = None
archive_id: int | None = None
stl_filename: str | None = None
- notes: str | None = None
+ remarks: str | None = None
class BOMItemUpdate(BaseModel):
@@ -151,10 +155,12 @@ class BOMItemUpdate(BaseModel):
name: str | None = None
quantity_needed: int | None = None
- quantity_printed: int | None = None
+ quantity_acquired: int | None = None
+ unit_price: float | None = None
+ sourcing_url: str | None = None
archive_id: int | None = None
stl_filename: str | None = None
- notes: str | None = None
+ remarks: str | None = None
class BOMItemResponse(BaseModel):
@@ -164,11 +170,13 @@ class BOMItemResponse(BaseModel):
project_id: int
name: str
quantity_needed: int
- quantity_printed: int
+ quantity_acquired: int
+ unit_price: float | None
+ sourcing_url: str | None
archive_id: int | None
archive_name: str | None = None
stl_filename: str | None
- notes: str | None
+ remarks: str | None
sort_order: int
is_complete: bool = False
created_at: datetime
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
index dd74db6ec..66d4dda06 100644
--- a/frontend/src/api/client.ts
+++ b/frontend/src/api/client.ts
@@ -384,6 +384,8 @@ export interface ArchivePreview {
print_name: string | null;
thumbnail_path: string | null;
status: string;
+ filament_type: string | null;
+ filament_color: string | null;
}
export interface ProjectListItem {
@@ -427,17 +429,19 @@ export interface ProjectUpdate {
parent_id?: number;
}
-// BOM Types
+// BOM Types - Tracks sourced/purchased parts (hardware, electronics, etc.)
export interface BOMItem {
id: number;
project_id: number;
name: string;
quantity_needed: number;
- quantity_printed: number;
+ quantity_acquired: number;
+ unit_price: number | null;
+ sourcing_url: string | null;
archive_id: number | null;
archive_name: string | null;
stl_filename: string | null;
- notes: string | null;
+ remarks: string | null;
sort_order: number;
is_complete: boolean;
created_at: string;
@@ -447,18 +451,22 @@ export interface BOMItem {
export interface BOMItemCreate {
name: string;
quantity_needed?: number;
+ unit_price?: number;
+ sourcing_url?: string;
archive_id?: number;
stl_filename?: string;
- notes?: string;
+ remarks?: string;
}
export interface BOMItemUpdate {
name?: string;
quantity_needed?: number;
- quantity_printed?: number;
+ quantity_acquired?: number;
+ unit_price?: number;
+ sourcing_url?: string;
archive_id?: number;
stl_filename?: string;
- notes?: string;
+ remarks?: string;
}
// Timeline Types
@@ -1572,10 +1580,12 @@ export const api = {
if (categories.notifications !== undefined) params.set('include_notifications', String(categories.notifications));
if (categories.templates !== undefined) params.set('include_templates', String(categories.templates));
if (categories.smart_plugs !== undefined) params.set('include_smart_plugs', String(categories.smart_plugs));
+ if (categories.external_links !== undefined) params.set('include_external_links', String(categories.external_links));
if (categories.printers !== undefined) params.set('include_printers', String(categories.printers));
if (categories.filaments !== undefined) params.set('include_filaments', String(categories.filaments));
if (categories.maintenance !== undefined) params.set('include_maintenance', String(categories.maintenance));
if (categories.archives !== undefined) params.set('include_archives', String(categories.archives));
+ if (categories.projects !== undefined) params.set('include_projects', String(categories.projects));
if (categories.access_codes !== undefined) params.set('include_access_codes', String(categories.access_codes));
}
const url = `${API_BASE}/settings/backup${params.toString() ? '?' + params.toString() : ''}`;
diff --git a/frontend/src/components/BackupModal.tsx b/frontend/src/components/BackupModal.tsx
index 20d8ec4fe..7e6af1bd7 100644
--- a/frontend/src/components/BackupModal.tsx
+++ b/frontend/src/components/BackupModal.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
-import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2, Key, AlertTriangle } from 'lucide-react';
+import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2, Key, AlertTriangle, Link, FolderKanban } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent } from './Card';
import { Button } from './Button';
@@ -47,6 +47,14 @@ const BACKUP_CATEGORIES: BackupCategory[] = [
default: true,
description: 'Tasmota plug configurations',
},
+ {
+ id: 'external_links',
+ labelKey: 'backup.categories.externalLinks',
+ defaultLabel: 'External Links',
+ icon: ,
+ default: true,
+ description: 'Sidebar links to external services',
+ },
{
id: 'printers',
labelKey: 'backup.categories.printers',
@@ -79,6 +87,14 @@ const BACKUP_CATEGORIES: BackupCategory[] = [
default: false,
description: 'All print data + files (3MF, thumbnails, photos)',
},
+ {
+ id: 'projects',
+ labelKey: 'backup.categories.projects',
+ defaultLabel: 'Projects',
+ icon:
Filament Cost
- ${stats.estimated_cost.toFixed(2)} + {currency}{stats.estimated_cost.toFixed(2)}
Budget
-${project.budget.toFixed(2)}
+{currency}{project.budget.toFixed(2)}
Remaining
= 0 ? 'text-bambu-green' : 'text-red-400'}`}> - ${(project.budget - stats.estimated_cost).toFixed(2)} + {currency}{(project.budget - stats.estimated_cost).toFixed(2)}
+ Upload any file: images (PNG, JPG), PDFs, STL files, or documents. +
+ {project.attachments && project.attachments.length > 0 ? (- No attachments. Upload STL files, reference images, or other documents. + No attachments yet. Click Upload to add files.
)} - {/* BOM Section */} + {/* BOM Section - Parts to source/purchase */}- {item.name} -
-- {item.quantity_printed} / {item.quantity_needed} printed -
++ {item.name} + + x{item.quantity_needed} + +
+ {item.unit_price !== null && ( + + {currency}{(item.unit_price * item.quantity_needed).toFixed(2)} + + )} ++ {item.remarks} +
+ )}- No parts in the bill of materials. Add parts to track what needs to be printed. + No parts in the bill of materials. Add hardware, electronics, or other components to track what needs to be sourced.
)} @@ -1011,6 +1152,18 @@ export function ProjectDetailPage() { isLoading={updateMutation.isPending} /> )} + + {/* Confirm Modal */} + {confirmModal.isOpen && ( ++ +{project.archive_count - 4} more +
+ )}+
Organize and track your 3D printing projects
i(pe,J)?(K[Y]=pe,K[ie]=ee,Y=ie):(K[Y]=J,K[$]=ee,Y=$);else if(iei(pe,ee))K[Y]=pe,K[ie]=ee,Y=ie;else break e}}return G}function i(K,G){var ee=K.sortIndex-G.sortIndex;return ee!==0?ee:K.id-G.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var a=performance;t.unstable_now=function(){return a.now()}}else{var s=Date,o=s.now();t.unstable_now=function(){return s.now()-o}}var c=[],u=[],d=1,f=null,g=3,p=!1,x=!1,v=!1,y=!1,b=typeof setTimeout=="function"?setTimeout:null,E=typeof clearTimeout=="function"?clearTimeout:null,w=typeof setImmediate<"u"?setImmediate:null;function A(K){for(var G=n(u);G!==null;){if(G.callback===null)r(u);else if(G.startTime<=K)r(u),G.sortIndex=G.expirationTime,e(c,G);else break;G=n(u)}}function T(K){if(v=!1,A(K),!x)if(n(c)!==null)x=!0,N||(N=!0,L());else{var G=n(u);G!==null&&W(T,G.startTime-K)}}var N=!1,C=-1,U=5,k=-1;function j(){return y?!0:!(t.unstable_now()-kK&&j());){var Y=f.callback;if(typeof Y=="function"){f.callback=null,g=f.priorityLevel;var Q=Y(f.expirationTime<=K);if(K=t.unstable_now(),typeof Q=="function"){f.callback=Q,A(K),G=!0;break t}f===n(c)&&r(c),A(K)}else r(c);f=n(c)}if(f!==null)G=!0;else{var z=n(u);z!==null&&W(T,z.startTime-K),G=!1}}break e}finally{f=null,g=ee,p=!1}G=void 0}}finally{G?L():N=!1}}}var L;if(typeof w=="function")L=function(){w(I)};else if(typeof MessageChannel<"u"){var q=new MessageChannel,R=q.port2;q.port1.onmessage=I,L=function(){R.postMessage(null)}}else L=function(){b(I,0)};function W(K,G){C=b(function(){K(t.unstable_now())},G)}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(K){K.callback=null},t.unstable_forceFrameRate=function(K){0>K||125Y?(K.sortIndex=ee,e(u,K),n(c)===null&&K===n(u)&&(v?(E(C),C=-1):v=!0,W(T,ee-Y))):(K.sortIndex=Q,e(c,K),x||p||(x=!0,N||(N=!0,L()))),K},t.unstable_shouldYield=j,t.unstable_wrapCallback=function(K){var G=g;return function(){var ee=g;g=G;try{return K.apply(this,arguments)}finally{g=ee}}}})(TA)),TA}var fF;function Ire(){return fF||(fF=1,AA.exports=Lre()),AA.exports}var CA={exports:{}},ia={};var mF;function Fre(){if(mF)return ia;mF=1;var t=Kp();function e(c){var u="https://react.dev/errors/"+c;if(1 "u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(e){console.error(e)}}return t(),CA.exports=Fre(),CA.exports}var gF;function Ure(){if(gF)return lx;gF=1;var t=Ire(),e=Kp(),n=C9();function r(h){var m="https://react.dev/errors/"+h;if(1 Q||(h.current=Y[Q],Y[Q]=null,Q--)}function J(h,m){Q++,Y[Q]=h.current,h.current=m}var ie=z(null),pe=z(null),fe=z(null),ye=z(null);function Re(h,m){switch(J(fe,m),J(pe,h),J(ie,null),m.nodeType){case 9:case 11:h=(h=m.documentElement)&&(h=h.namespaceURI)?P5(h):0;break;default:if(h=m.tagName,m=m.namespaceURI)m=P5(m),h=R5(m,h);else switch(h){case"svg":h=1;break;case"math":h=2;break;default:h=0}}$(ie),J(ie,h)}function re(){$(ie),$(pe),$(fe)}function we(h){h.memoizedState!==null&&J(ye,h);var m=ie.current,_=R5(m,h.type);m!==_&&(J(pe,h),J(ie,_))}function Oe(h){pe.current===h&&($(ie),$(pe)),ye.current===h&&($(ye),rx._currentValue=ee)}var ce,Ie;function B(h){if(ce===void 0)try{throw Error()}catch(_){var m=_.stack.trim().match(/\n( *(at )?)/);ce=m&&m[1]||"",Ie=-1<_.stack.indexOf(` + at`)?" ( )":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` +`+ce+h+Ie}var H=!1;function ne(h,m){if(!h||H)return"";H=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var S={DetermineComponentFrameRoot:function(){try{if(m){var ht=function(){throw Error()};if(Object.defineProperty(ht.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ht,[])}catch(st){var Je=st}Reflect.construct(h,[],ht)}else{try{ht.call()}catch(st){Je=st}h.call(ht.prototype)}}else{try{throw Error()}catch(st){Je=st}(ht=h())&&typeof ht.catch=="function"&&ht.catch(function(){})}}catch(st){if(st&&Je&&typeof st.stack=="string")return[st.stack,Je.stack]}return[null,null]}};S.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var P=Object.getOwnPropertyDescriptor(S.DetermineComponentFrameRoot,"name");P&&P.configurable&&Object.defineProperty(S.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var D=S.DetermineComponentFrameRoot(),X=D[0],le=D[1];if(X&&le){var Te=X.split(` +`),Ze=le.split(` +`);for(P=S=0;S P||Te[S]!==Ze[P]){var ct=` +`+Te[S].replace(" at new "," at ");return h.displayName&&ct.includes(" ")&&(ct=ct.replace(" ",h.displayName)),ct}while(1<=S&&0<=P);break}}}finally{H=!1,Error.prepareStackTrace=_}return(_=h?h.displayName||h.name:"")?B(_):""}function V(h,m){switch(h.tag){case 26:case 27:case 5:return B(h.type);case 16:return B("Lazy");case 13:return h.child!==m&&m!==null?B("Suspense Fallback"):B("Suspense");case 19:return B("SuspenseList");case 0:case 15:return ne(h.type,!1);case 11:return ne(h.type.render,!1);case 1:return ne(h.type,!0);case 31:return B("Activity");default:return""}}function Z(h){try{var m="",_=null;do m+=V(h,_),_=h,h=h.return;while(h);return m}catch(S){return` +Error generating stack: `+S.message+` +`+S.stack}}var se=Object.prototype.hasOwnProperty,xe=t.unstable_scheduleCallback,Ee=t.unstable_cancelCallback,Me=t.unstable_shouldYield,F=t.unstable_requestPaint,O=t.unstable_now,te=t.unstable_getCurrentPriorityLevel,ve=t.unstable_ImmediatePriority,Se=t.unstable_UserBlockingPriority,be=t.unstable_NormalPriority,rt=t.unstable_LowPriority,He=t.unstable_IdlePriority,de=t.log,We=t.unstable_setDisableYieldValue,Ue=null,oe=null;function tt(h){if(typeof de=="function"&&We(h),oe&&typeof oe.setStrictMode=="function")try{oe.setStrictMode(Ue,h)}catch{}}var $e=Math.clz32?Math.clz32:he,Ye=Math.log,ut=Math.LN2;function he(h){return h>>>=0,h===0?32:31-(Ye(h)/ut|0)|0}var Qe=256,ge=262144,ze=4194304;function ke(h){var m=h&42;if(m!==0)return m;switch(h&-h){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 h&261888;case 262144:case 524288:case 1048576:case 2097152:return h&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return h&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return h}}function _e(h,m,_){var S=h.pendingLanes;if(S===0)return 0;var P=0,D=h.suspendedLanes,X=h.pingedLanes;h=h.warmLanes;var le=S&134217727;return le!==0?(S=le&~D,S!==0?P=ke(S):(X&=le,X!==0?P=ke(X):_||(_=le&~h,_!==0&&(P=ke(_))))):(le=S&~D,le!==0?P=ke(le):X!==0?P=ke(X):_||(_=S&~h,_!==0&&(P=ke(_)))),P===0?0:m!==0&&m!==P&&(m&D)===0&&(D=P&-P,_=m&-m,D>=_||D===32&&(_&4194048)!==0)?m:P}function ue(h,m){return(h.pendingLanes&~(h.suspendedLanes&~h.pingedLanes)&m)===0}function ot(h,m){switch(h){case 1:case 2:case 4:case 8:case 64:return m+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 m+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 xt(){var h=ze;return ze<<=1,(ze&62914560)===0&&(ze=4194304),h}function Et(h){for(var m=[],_=0;31>_;_++)m.push(h);return m}function Qt(h,m){h.pendingLanes|=m,m!==268435456&&(h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0)}function Ot(h,m,_,S,P,D){var X=h.pendingLanes;h.pendingLanes=_,h.suspendedLanes=0,h.pingedLanes=0,h.warmLanes=0,h.expiredLanes&=_,h.entangledLanes&=_,h.errorRecoveryDisabledLanes&=_,h.shellSuspendCounter=0;var le=h.entanglements,Te=h.expirationTimes,Ze=h.hiddenUpdates;for(_=X&~_;0<_;){var ct=31-$e(_),ht=1< "u")return null;try{return h.activeElement||h.body}catch{return h.body}}var Ir=/[\n"\\]/g;function jn(h){return h.replace(Ir,function(m){return"\\"+m.charCodeAt(0).toString(16)+" "})}function xo(h,m,_,S,P,D,X,le){h.name="",X!=null&&typeof X!="function"&&typeof X!="symbol"&&typeof X!="boolean"?h.type=X:h.removeAttribute("type"),m!=null?X==="number"?(m===0&&h.value===""||h.value!=m)&&(h.value=""+hn(m)):h.value!==""+hn(m)&&(h.value=""+hn(m)):X!=="submit"&&X!=="reset"||h.removeAttribute("value"),m!=null?Ai(h,X,hn(m)):_!=null?Ai(h,X,hn(_)):S!=null&&h.removeAttribute("value"),P==null&&D!=null&&(h.defaultChecked=!!D),P!=null&&(h.checked=P&&typeof P!="function"&&typeof P!="symbol"),le!=null&&typeof le!="function"&&typeof le!="symbol"&&typeof le!="boolean"?h.name=""+hn(le):h.removeAttribute("name")}function Ln(h,m,_,S,P,D,X,le){if(D!=null&&typeof D!="function"&&typeof D!="symbol"&&typeof D!="boolean"&&(h.type=D),m!=null||_!=null){if(!(D!=="submit"&&D!=="reset"||m!=null)){Jt(h);return}_=_!=null?""+hn(_):"",m=m!=null?""+hn(m):_,le||m===h.value||(h.value=m),h.defaultValue=m}S=S??P,S=typeof S!="function"&&typeof S!="symbol"&&!!S,h.checked=le?h.checked:!!S,h.defaultChecked=!!S,X!=null&&typeof X!="function"&&typeof X!="symbol"&&typeof X!="boolean"&&(h.name=X),Jt(h)}function Ai(h,m,_){m==="number"&&Cr(h.ownerDocument)===h||h.defaultValue===""+_||(h.defaultValue=""+_)}function li(h,m,_,S){if(h=h.options,m){m={};for(var P=0;P<_.length;P++)m["$"+_[P]]=!0;for(_=0;_ "u"||typeof window.document>"u"||typeof window.document.createElement>"u"),SE=!1;if(Pl)try{var _g={};Object.defineProperty(_g,"passive",{get:function(){SE=!0}}),window.addEventListener("test",_g,_g),window.removeEventListener("test",_g,_g)}catch{SE=!1}var Zc=null,ME=null,oy=null;function RL(){if(oy)return oy;var h,m=ME,_=m.length,S,P="value"in Zc?Zc.value:Zc.textContent,D=P.length;for(h=0;h<_&&m[h]===P[h];h++);var X=_-h;for(S=1;S<=X&&m[_-S]===P[D-S];S++);return oy=P.slice(h,1 =Mg),FL=" ",UL=!1;function zL(h,m){switch(h){case"keyup":return Jte.indexOf(m.keyCode)!==-1;case"keydown":return m.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function BL(h){return h=h.detail,typeof h=="object"&&"data"in h?h.data:null}var Sf=!1;function tne(h,m){switch(h){case"compositionend":return BL(m);case"keypress":return m.which!==32?null:(UL=!0,FL);case"textInput":return h=m.data,h===FL&&UL?null:h;default:return null}}function nne(h,m){if(Sf)return h==="compositionend"||!NE&&zL(h,m)?(h=RL(),oy=ME=Zc=null,Sf=!1,h):null;switch(h){case"paste":return null;case"keypress":if(!(m.ctrlKey||m.altKey||m.metaKey)||m.ctrlKey&&m.altKey){if(m.char&&1=m)return{node:_,offset:m-h};h=S}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=XL(_)}}function ZL(h,m){return h&&m?h===m?!0:h&&h.nodeType===3?!1:m&&m.nodeType===3?ZL(h,m.parentNode):"contains"in h?h.contains(m):h.compareDocumentPosition?!!(h.compareDocumentPosition(m)&16):!1:!1}function QL(h){h=h!=null&&h.ownerDocument!=null&&h.ownerDocument.defaultView!=null?h.ownerDocument.defaultView:window;for(var m=Cr(h.document);m instanceof h.HTMLIFrameElement;){try{var _=typeof m.contentWindow.location.href=="string"}catch{_=!1}if(_)h=m.contentWindow;else break;m=Cr(h.document)}return m}function RE(h){var m=h&&h.nodeName&&h.nodeName.toLowerCase();return m&&(m==="input"&&(h.type==="text"||h.type==="search"||h.type==="tel"||h.type==="url"||h.type==="password")||m==="textarea"||h.contentEditable==="true")}var une=Pl&&"documentMode"in document&&11>=document.documentMode,Mf=null,jE=null,Cg=null,DE=!1;function JL(h,m,_){var S=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;DE||Mf==null||Mf!==Cr(S)||(S=Mf,"selectionStart"in S&&RE(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}),Cg&&Tg(Cg,S)||(Cg=S,S=ev(jE,"onSelect"),0 >=X,P-=X,Wo=1<<32-$e(m)+P|_< rn?(vn=It,It=null):vn=It.sibling;var Cn=Je(Be,It,Xe[rn],dt);if(Cn===null){It===null&&(It=vn);break}h&&It&&Cn.alternate===null&&m(Be,It),je=D(Cn,je,rn),Tn===null?zt=Cn:Tn.sibling=Cn,Tn=Cn,It=vn}if(rn===Xe.length)return _(Be,It),wn&&jl(Be,rn),zt;if(It===null){for(;rn
rn?(vn=It,It=null):vn=It.sibling;var yu=Je(Be,It,Cn.value,dt);if(yu===null){It===null&&(It=vn);break}h&&It&&yu.alternate===null&&m(Be,It),je=D(yu,je,rn),Tn===null?zt=yu:Tn.sibling=yu,Tn=yu,It=vn}if(Cn.done)return _(Be,It),wn&&jl(Be,rn),zt;if(It===null){for(;!Cn.done;rn++,Cn=Xe.next())Cn=ht(Be,Cn.value,dt),Cn!==null&&(je=D(Cn,je,rn),Tn===null?zt=Cn:Tn.sibling=Cn,Tn=Cn);return wn&&jl(Be,rn),zt}for(It=S(It);!Cn.done;rn++,Cn=Xe.next())Cn=st(It,Be,rn,Cn.value,dt),Cn!==null&&(h&&Cn.alternate!==null&&It.delete(Cn.key===null?rn:Cn.key),je=D(Cn,je,rn),Tn===null?zt=Cn:Tn.sibling=Cn,Tn=Cn);return h&&It.forEach(function(kre){return m(Be,kre)}),wn&&jl(Be,rn),zt}function Wn(Be,je,Xe,dt){if(typeof Xe=="object"&&Xe!==null&&Xe.type===v&&Xe.key===null&&(Xe=Xe.props.children),typeof Xe=="object"&&Xe!==null){switch(Xe.$$typeof){case p:e:{for(var zt=Xe.key;je!==null;){if(je.key===zt){if(zt=Xe.type,zt===v){if(je.tag===7){_(Be,je.sibling),dt=P(je,Xe.props.children),dt.return=Be,Be=dt;break e}}else if(je.elementType===zt||typeof zt=="object"&&zt!==null&&zt.$$typeof===U&&$d(zt)===je.type){_(Be,je.sibling),dt=P(je,Xe.props),Dg(dt,Xe),dt.return=Be,Be=dt;break e}_(Be,je);break}else m(Be,je);je=je.sibling}Xe.type===v?(dt=Fd(Xe.props.children,Be.mode,dt,Xe.key),dt.return=Be,Be=dt):(dt=xy(Xe.type,Xe.key,Xe.props,null,Be.mode,dt),Dg(dt,Xe),dt.return=Be,Be=dt)}return X(Be);case x:e:{for(zt=Xe.key;je!==null;){if(je.key===zt)if(je.tag===4&&je.stateNode.containerInfo===Xe.containerInfo&&je.stateNode.implementation===Xe.implementation){_(Be,je.sibling),dt=P(je,Xe.children||[]),dt.return=Be,Be=dt;break e}else{_(Be,je);break}else m(Be,je);je=je.sibling}dt=BE(Xe,Be.mode,dt),dt.return=Be,Be=dt}return X(Be);case U:return Xe=$d(Xe),Wn(Be,je,Xe,dt)}if(W(Xe))return Dt(Be,je,Xe,dt);if(L(Xe)){if(zt=L(Xe),typeof zt!="function")throw Error(r(150));return Xe=zt.call(Xe),Gt(Be,je,Xe,dt)}if(typeof Xe.then=="function")return Wn(Be,je,My(Xe),dt);if(Xe.$$typeof===w)return Wn(Be,je,vy(Be,Xe),dt);Ey(Be,Xe)}return typeof Xe=="string"&&Xe!==""||typeof Xe=="number"||typeof Xe=="bigint"?(Xe=""+Xe,je!==null&&je.tag===6?(_(Be,je.sibling),dt=P(je,Xe),dt.return=Be,Be=dt):(_(Be,je),dt=zE(Xe,Be.mode,dt),dt.return=Be,Be=dt),X(Be)):_(Be,je)}return function(Be,je,Xe,dt){try{jg=0;var zt=Wn(Be,je,Xe,dt);return Of=null,zt}catch(It){if(It===Df||It===wy)throw It;var Tn=cs(29,It,null,Be.mode);return Tn.lanes=dt,Tn.return=Be,Tn}finally{}}}var Gd=w4(!0),S4=w4(!1),nu=!1;function JE(h){h.updateQueue={baseState:h.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function e2(h,m){h=h.updateQueue,m.updateQueue===h&&(m.updateQueue={baseState:h.baseState,firstBaseUpdate:h.firstBaseUpdate,lastBaseUpdate:h.lastBaseUpdate,shared:h.shared,callbacks:null})}function ru(h){return{lane:h,tag:0,payload:null,callback:null,next:null}}function iu(h,m,_){var S=h.updateQueue;if(S===null)return null;if(S=S.shared,(Pn&2)!==0){var P=S.pending;return P===null?m.next=m:(m.next=P.next,P.next=m),S.pending=m,m=gy(h),s4(h,null,_),m}return py(h,S,m,_),gy(h)}function Og(h,m,_){if(m=m.updateQueue,m!==null&&(m=m.shared,(_&4194048)!==0)){var S=m.lanes;S&=h.pendingLanes,_|=S,m.lanes=_,On(h,_)}}function t2(h,m){var _=h.updateQueue,S=h.alternate;if(S!==null&&(S=S.updateQueue,_===S)){var P=null,D=null;if(_=_.firstBaseUpdate,_!==null){do{var X={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};D===null?P=D=X:D=D.next=X,_=_.next}while(_!==null);D===null?P=D=m:D=D.next=m}else P=D=m;_={baseState:S.baseState,firstBaseUpdate:P,lastBaseUpdate:D,shared:S.shared,callbacks:S.callbacks},h.updateQueue=_;return}h=_.lastBaseUpdate,h===null?_.firstBaseUpdate=m:h.next=m,_.lastBaseUpdate=m}var n2=!1;function Lg(){if(n2){var h=jf;if(h!==null)throw h}}function Ig(h,m,_,S){n2=!1;var P=h.updateQueue;nu=!1;var D=P.firstBaseUpdate,X=P.lastBaseUpdate,le=P.shared.pending;if(le!==null){P.shared.pending=null;var Te=le,Ze=Te.next;Te.next=null,X===null?D=Ze:X.next=Ze,X=Te;var ct=h.alternate;ct!==null&&(ct=ct.updateQueue,le=ct.lastBaseUpdate,le!==X&&(le===null?ct.firstBaseUpdate=Ze:le.next=Ze,ct.lastBaseUpdate=Te))}if(D!==null){var ht=P.baseState;X=0,ct=Ze=Te=null,le=D;do{var Je=le.lane&-536870913,st=Je!==le.lane;if(st?(yn&Je)===Je:(S&Je)===Je){Je!==0&&Je===Rf&&(n2=!0),ct!==null&&(ct=ct.next={lane:0,tag:le.tag,payload:le.payload,callback:null,next:null});e:{var Dt=h,Gt=le;Je=m;var Wn=_;switch(Gt.tag){case 1:if(Dt=Gt.payload,typeof Dt=="function"){ht=Dt.call(Wn,ht,Je);break e}ht=Dt;break e;case 3:Dt.flags=Dt.flags&-65537|128;case 0:if(Dt=Gt.payload,Je=typeof Dt=="function"?Dt.call(Wn,ht,Je):Dt,Je==null)break e;ht=f({},ht,Je);break e;case 2:nu=!0}}Je=le.callback,Je!==null&&(h.flags|=64,st&&(h.flags|=8192),st=P.callbacks,st===null?P.callbacks=[Je]:st.push(Je))}else st={lane:Je,tag:le.tag,payload:le.payload,callback:le.callback,next:null},ct===null?(Ze=ct=st,Te=ht):ct=ct.next=st,X|=Je;if(le=le.next,le===null){if(le=P.shared.pending,le===null)break;st=le,le=st.next,st.next=null,P.lastBaseUpdate=st,P.shared.pending=null}}while(!0);ct===null&&(Te=ht),P.baseState=Te,P.firstBaseUpdate=Ze,P.lastBaseUpdate=ct,D===null&&(P.shared.lanes=0),cu|=X,h.lanes=X,h.memoizedState=ht}}function M4(h,m){if(typeof h!="function")throw Error(r(191,h));h.call(m)}function E4(h,m){var _=h.callbacks;if(_!==null)for(h.callbacks=null,h=0;h<_.length;h++)M4(_[h],m)}var Lf=z(null),Ay=z(0);function A4(h,m){h=Vl,J(Ay,h),J(Lf,m),Vl=h|m.baseLanes}function r2(){J(Ay,Vl),J(Lf,Lf.current)}function i2(){Vl=Ay.current,$(Lf),$(Ay)}var us=z(null),Ls=null;function au(h){var m=h.alternate;J(Fr,Fr.current&1),J(us,h),Ls===null&&(m===null||Lf.current!==null||m.memoizedState!==null)&&(Ls=h)}function a2(h){J(Fr,Fr.current),J(us,h),Ls===null&&(Ls=h)}function T4(h){h.tag===22?(J(Fr,Fr.current),J(us,h),Ls===null&&(Ls=h)):su()}function su(){J(Fr,Fr.current),J(us,us.current)}function ds(h){$(us),Ls===h&&(Ls=null),$(Fr)}var Fr=z(0);function Ty(h){for(var m=h;m!==null;){if(m.tag===13){var _=m.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||dA(_)||hA(_)))return m}else if(m.tag===19&&(m.memoizedProps.revealOrder==="forwards"||m.memoizedProps.revealOrder==="backwards"||m.memoizedProps.revealOrder==="unstable_legacy-backwards"||m.memoizedProps.revealOrder==="together")){if((m.flags&128)!==0)return m}else if(m.child!==null){m.child.return=m,m=m.child;continue}if(m===h)break;for(;m.sibling===null;){if(m.return===null||m.return===h)return null;m=m.return}m.sibling.return=m.return,m=m.sibling}return null}var Ll=0,nn=null,Vn=null,Kr=null,Cy=!1,If=!1,Wd=!1,Ny=0,Fg=0,Ff=null,vne=0;function Nr(){throw Error(r(321))}function s2(h,m){if(m===null)return!1;for(var _=0;_ D?D:8;var X=K.T,le={};K.T=le,_2(h,!1,m,_);try{var Te=P(),Ze=K.S;if(Ze!==null&&Ze(le,Te),Te!==null&&typeof Te=="object"&&typeof Te.then=="function"){var ct=yne(Te,S);zg(h,m,ct,ms(h))}else zg(h,m,S,ms(h))}catch(ht){zg(h,m,{then:function(){},status:"rejected",reason:ht},ms())}finally{G.p=D,X!==null&&le.types!==null&&(X.types=le.types),K.T=X}}function Ene(){}function y2(h,m,_,S){if(h.tag!==5)throw Error(r(476));var P=rI(h).queue;nI(h,P,m,ee,_===null?Ene:function(){return iI(h),_(S)})}function rI(h){var m=h.memoizedState;if(m!==null)return m;m={memoizedState:ee,baseState:ee,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Il,lastRenderedState:ee},next:null};var _={};return m.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Il,lastRenderedState:_},next:null},h.memoizedState=m,h=h.alternate,h!==null&&(h.memoizedState=m),m}function iI(h){var m=rI(h);m.next===null&&(m=h.alternate.memoizedState),zg(h,m.next.queue,{},ms())}function v2(){return $i(rx)}function aI(){return Ur().memoizedState}function sI(){return Ur().memoizedState}function Ane(h){for(var m=h.return;m!==null;){switch(m.tag){case 24:case 3:var _=ms();h=ru(_);var S=iu(m,h,_);S!==null&&(Ba(S,m,_),Og(S,m,_)),m={cache:XE()},h.payload=m;return}m=m.return}}function Tne(h,m,_){var S=ms();_={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Oy(h)?lI(m,_):(_=FE(h,m,_,S),_!==null&&(Ba(_,h,S),cI(_,m,S)))}function oI(h,m,_){var S=ms();zg(h,m,_,S)}function zg(h,m,_,S){var P={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(Oy(h))lI(m,P);else{var D=h.alternate;if(h.lanes===0&&(D===null||D.lanes===0)&&(D=m.lastRenderedReducer,D!==null))try{var X=m.lastRenderedState,le=D(X,_);if(P.hasEagerState=!0,P.eagerState=le,ls(le,X))return py(h,m,P,0),er===null&&my(),!1}catch{}finally{}if(_=FE(h,m,P,S),_!==null)return Ba(_,h,S),cI(_,m,S),!0}return!1}function _2(h,m,_,S){if(S={lane:2,revertLane:J2(),gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},Oy(h)){if(m)throw Error(r(479))}else m=FE(h,_,S,2),m!==null&&Ba(m,h,2)}function Oy(h){var m=h.alternate;return h===nn||m!==null&&m===nn}function lI(h,m){If=Cy=!0;var _=h.pending;_===null?m.next=m:(m.next=_.next,_.next=m),h.pending=m}function cI(h,m,_){if((_&4194048)!==0){var S=m.lanes;S&=h.pendingLanes,_|=S,m.lanes=_,On(h,_)}}var Bg={readContext:$i,use:Py,useCallback:Nr,useContext:Nr,useEffect:Nr,useImperativeHandle:Nr,useLayoutEffect:Nr,useInsertionEffect:Nr,useMemo:Nr,useReducer:Nr,useRef:Nr,useState:Nr,useDebugValue:Nr,useDeferredValue:Nr,useTransition:Nr,useSyncExternalStore:Nr,useId:Nr,useHostTransitionStatus:Nr,useFormState:Nr,useActionState:Nr,useOptimistic:Nr,useMemoCache:Nr,useCacheRefresh:Nr};Bg.useEffectEvent=Nr;var uI={readContext:$i,use:Py,useCallback:function(h,m){return ya().memoizedState=[h,m===void 0?null:m],h},useContext:$i,useEffect:q4,useImperativeHandle:function(h,m,_){_=_!=null?_.concat([h]):null,jy(4194308,4,Z4.bind(null,m,h),_)},useLayoutEffect:function(h,m){return jy(4194308,4,h,m)},useInsertionEffect:function(h,m){jy(4,2,h,m)},useMemo:function(h,m){var _=ya();m=m===void 0?null:m;var S=h();if(Wd){tt(!0);try{h()}finally{tt(!1)}}return _.memoizedState=[S,m],S},useReducer:function(h,m,_){var S=ya();if(_!==void 0){var P=_(m);if(Wd){tt(!0);try{_(m)}finally{tt(!1)}}}else P=m;return S.memoizedState=S.baseState=P,h={pending:null,lanes:0,dispatch:null,lastRenderedReducer:h,lastRenderedState:P},S.queue=h,h=h.dispatch=Tne.bind(null,nn,h),[S.memoizedState,h]},useRef:function(h){var m=ya();return h={current:h},m.memoizedState=h},useState:function(h){h=m2(h);var m=h.queue,_=oI.bind(null,nn,m);return m.dispatch=_,[h.memoizedState,_]},useDebugValue:x2,useDeferredValue:function(h,m){var _=ya();return b2(_,h,m)},useTransition:function(){var h=m2(!1);return h=nI.bind(null,nn,h.queue,!0,!1),ya().memoizedState=h,[!1,h]},useSyncExternalStore:function(h,m,_){var S=nn,P=ya();if(wn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=m(),er===null)throw Error(r(349));(yn&127)!==0||P4(S,m,_)}P.memoizedState=_;var D={value:_,getSnapshot:m};return P.queue=D,q4(j4.bind(null,S,D,h),[h]),S.flags|=2048,Uf(9,{destroy:void 0},R4.bind(null,S,D,_,m),null),_},useId:function(){var h=ya(),m=er.identifierPrefix;if(wn){var _=qo,S=Wo;_=(S&~(1<<32-$e(S)-1)).toString(32)+_,m="_"+m+"R_"+_,_=Ny++,0<_&&(m+="H"+_.toString(32)),m+="_"}else _=vne++,m="_"+m+"r_"+_.toString(32)+"_";return h.memoizedState=m},useHostTransitionStatus:v2,useFormState:H4,useActionState:H4,useOptimistic:function(h){var m=ya();m.memoizedState=m.baseState=h;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return m.queue=_,m=_2.bind(null,nn,!0,_),_.dispatch=m,[h,m]},useMemoCache:d2,useCacheRefresh:function(){return ya().memoizedState=Ane.bind(null,nn)},useEffectEvent:function(h){var m=ya(),_={impl:h};return m.memoizedState=_,function(){if((Pn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},w2={readContext:$i,use:Py,useCallback:J4,useContext:$i,useEffect:g2,useImperativeHandle:Q4,useInsertionEffect:X4,useLayoutEffect:Y4,useMemo:eI,useReducer:Ry,useRef:W4,useState:function(){return Ry(Il)},useDebugValue:x2,useDeferredValue:function(h,m){var _=Ur();return tI(_,Vn.memoizedState,h,m)},useTransition:function(){var h=Ry(Il)[0],m=Ur().memoizedState;return[typeof h=="boolean"?h:Ug(h),m]},useSyncExternalStore:k4,useId:aI,useHostTransitionStatus:v2,useFormState:$4,useActionState:$4,useOptimistic:function(h,m){var _=Ur();return L4(_,Vn,h,m)},useMemoCache:d2,useCacheRefresh:sI};w2.useEffectEvent=K4;var dI={readContext:$i,use:Py,useCallback:J4,useContext:$i,useEffect:g2,useImperativeHandle:Q4,useInsertionEffect:X4,useLayoutEffect:Y4,useMemo:eI,useReducer:f2,useRef:W4,useState:function(){return f2(Il)},useDebugValue:x2,useDeferredValue:function(h,m){var _=Ur();return Vn===null?b2(_,h,m):tI(_,Vn.memoizedState,h,m)},useTransition:function(){var h=f2(Il)[0],m=Ur().memoizedState;return[typeof h=="boolean"?h:Ug(h),m]},useSyncExternalStore:k4,useId:aI,useHostTransitionStatus:v2,useFormState:G4,useActionState:G4,useOptimistic:function(h,m){var _=Ur();return Vn!==null?L4(_,Vn,h,m):(_.baseState=h,[h,_.queue.dispatch])},useMemoCache:d2,useCacheRefresh:sI};dI.useEffectEvent=K4;function S2(h,m,_,S){m=h.memoizedState,_=_(S,m),_=_==null?m:f({},m,_),h.memoizedState=_,h.lanes===0&&(h.updateQueue.baseState=_)}var M2={enqueueSetState:function(h,m,_){h=h._reactInternals;var S=ms(),P=ru(S);P.payload=m,_!=null&&(P.callback=_),m=iu(h,P,S),m!==null&&(Ba(m,h,S),Og(m,h,S))},enqueueReplaceState:function(h,m,_){h=h._reactInternals;var S=ms(),P=ru(S);P.tag=1,P.payload=m,_!=null&&(P.callback=_),m=iu(h,P,S),m!==null&&(Ba(m,h,S),Og(m,h,S))},enqueueForceUpdate:function(h,m){h=h._reactInternals;var _=ms(),S=ru(_);S.tag=2,m!=null&&(S.callback=m),m=iu(h,S,_),m!==null&&(Ba(m,h,_),Og(m,h,_))}};function hI(h,m,_,S,P,D,X){return h=h.stateNode,typeof h.shouldComponentUpdate=="function"?h.shouldComponentUpdate(S,D,X):m.prototype&&m.prototype.isPureReactComponent?!Tg(_,S)||!Tg(P,D):!0}function fI(h,m,_,S){h=m.state,typeof m.componentWillReceiveProps=="function"&&m.componentWillReceiveProps(_,S),typeof m.UNSAFE_componentWillReceiveProps=="function"&&m.UNSAFE_componentWillReceiveProps(_,S),m.state!==h&&M2.enqueueReplaceState(m,m.state,null)}function qd(h,m){var _=m;if("ref"in m){_={};for(var S in m)S!=="ref"&&(_[S]=m[S])}if(h=h.defaultProps){_===m&&(_=f({},_));for(var P in h)_[P]===void 0&&(_[P]=h[P])}return _}function mI(h){fy(h)}function pI(h){console.error(h)}function gI(h){fy(h)}function Ly(h,m){try{var _=h.onUncaughtError;_(m.value,{componentStack:m.stack})}catch(S){setTimeout(function(){throw S})}}function xI(h,m,_){try{var S=h.onCaughtError;S(_.value,{componentStack:_.stack,errorBoundary:m.tag===1?m.stateNode:null})}catch(P){setTimeout(function(){throw P})}}function E2(h,m,_){return _=ru(_),_.tag=3,_.payload={element:null},_.callback=function(){Ly(h,m)},_}function bI(h){return h=ru(h),h.tag=3,h}function yI(h,m,_,S){var P=_.type.getDerivedStateFromError;if(typeof P=="function"){var D=S.value;h.payload=function(){return P(D)},h.callback=function(){xI(m,_,S)}}var X=_.stateNode;X!==null&&typeof X.componentDidCatch=="function"&&(h.callback=function(){xI(m,_,S),typeof P!="function"&&(uu===null?uu=new Set([this]):uu.add(this));var le=S.stack;this.componentDidCatch(S.value,{componentStack:le!==null?le:""})})}function Cne(h,m,_,S,P){if(_.flags|=32768,S!==null&&typeof S=="object"&&typeof S.then=="function"){if(m=_.alternate,m!==null&&Pf(m,_,P,!0),_=us.current,_!==null){switch(_.tag){case 31:case 13:return Ls===null?Ky():_.alternate===null&&kr===0&&(kr=3),_.flags&=-257,_.flags|=65536,_.lanes=P,S===Sy?_.flags|=16384:(m=_.updateQueue,m===null?_.updateQueue=new Set([S]):m.add(S),Y2(h,S,P)),!1;case 22:return _.flags|=65536,S===Sy?_.flags|=16384:(m=_.updateQueue,m===null?(m={transitions:null,markerInstances:null,retryQueue:new Set([S])},_.updateQueue=m):(_=m.retryQueue,_===null?m.retryQueue=new Set([S]):_.add(S)),Y2(h,S,P)),!1}throw Error(r(435,_.tag))}return Y2(h,S,P),Ky(),!1}if(wn)return m=us.current,m!==null?((m.flags&65536)===0&&(m.flags|=256),m.flags|=65536,m.lanes=P,S!==VE&&(h=Error(r(422),{cause:S}),kg(Rs(h,_)))):(S!==VE&&(m=Error(r(423),{cause:S}),kg(Rs(m,_))),h=h.current.alternate,h.flags|=65536,P&=-P,h.lanes|=P,S=Rs(S,_),P=E2(h.stateNode,S,P),t2(h,P),kr!==4&&(kr=2)),!1;var D=Error(r(520),{cause:S});if(D=Rs(D,_),Xg===null?Xg=[D]:Xg.push(D),kr!==4&&(kr=2),m===null)return!0;S=Rs(S,_),_=m;do{switch(_.tag){case 3:return _.flags|=65536,h=P&-P,_.lanes|=h,h=E2(_.stateNode,S,h),t2(_,h),!1;case 1:if(m=_.type,D=_.stateNode,(_.flags&128)===0&&(typeof m.getDerivedStateFromError=="function"||D!==null&&typeof D.componentDidCatch=="function"&&(uu===null||!uu.has(D))))return _.flags|=65536,P&=-P,_.lanes|=P,P=bI(P),yI(P,h,_,S),t2(_,P),!1}_=_.return}while(_!==null);return!1}var A2=Error(r(461)),Xr=!1;function Vi(h,m,_,S){m.child=h===null?S4(m,null,_,S):Gd(m,h.child,_,S)}function vI(h,m,_,S,P){_=_.render;var D=m.ref;if("ref"in S){var X={};for(var le in S)le!=="ref"&&(X[le]=S[le])}else X=S;return Bd(m),S=o2(h,m,_,X,D,P),le=l2(),h!==null&&!Xr?(c2(h,m,P),Fl(h,m,P)):(wn&&le&&HE(m),m.flags|=1,Vi(h,m,S,P),m.child)}function _I(h,m,_,S,P){if(h===null){var D=_.type;return typeof D=="function"&&!UE(D)&&D.defaultProps===void 0&&_.compare===null?(m.tag=15,m.type=D,wI(h,m,D,S,P)):(h=xy(_.type,null,S,m,m.mode,P),h.ref=m.ref,h.return=m,m.child=h)}if(D=h.child,!D2(h,P)){var X=D.memoizedProps;if(_=_.compare,_=_!==null?_:Tg,_(X,S)&&h.ref===m.ref)return Fl(h,m,P)}return m.flags|=1,h=Rl(D,S),h.ref=m.ref,h.return=m,m.child=h}function wI(h,m,_,S,P){if(h!==null){var D=h.memoizedProps;if(Tg(D,S)&&h.ref===m.ref)if(Xr=!1,m.pendingProps=S=D,D2(h,P))(h.flags&131072)!==0&&(Xr=!0);else return m.lanes=h.lanes,Fl(h,m,P)}return T2(h,m,_,S,P)}function SI(h,m,_,S){var P=S.children,D=h!==null?h.memoizedState:null;if(h===null&&m.stateNode===null&&(m.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),S.mode==="hidden"){if((m.flags&128)!==0){if(D=D!==null?D.baseLanes|_:_,h!==null){for(S=m.child=h.child,P=0;S!==null;)P=P|S.lanes|S.childLanes,S=S.sibling;S=P&~D}else S=0,m.child=null;return MI(h,m,D,_,S)}if((_&536870912)!==0)m.memoizedState={baseLanes:0,cachePool:null},h!==null&&_y(m,D!==null?D.cachePool:null),D!==null?A4(m,D):r2(),T4(m);else return S=m.lanes=536870912,MI(h,m,D!==null?D.baseLanes|_:_,_,S)}else D!==null?(_y(m,D.cachePool),A4(m,D),su(),m.memoizedState=null):(h!==null&&_y(m,null),r2(),su());return Vi(h,m,P,_),m.child}function Hg(h,m){return h!==null&&h.tag===22||m.stateNode!==null||(m.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),m.sibling}function MI(h,m,_,S,P){var D=ZE();return D=D===null?null:{parent:qr._currentValue,pool:D},m.memoizedState={baseLanes:_,cachePool:D},h!==null&&_y(m,null),r2(),T4(m),h!==null&&Pf(h,m,S,!0),m.childLanes=P,null}function Iy(h,m){return m=Uy({mode:m.mode,children:m.children},h.mode),m.ref=h.ref,h.child=m,m.return=h,m}function EI(h,m,_){return Gd(m,h.child,null,_),h=Iy(m,m.pendingProps),h.flags|=2,ds(m),m.memoizedState=null,h}function Nne(h,m,_){var S=m.pendingProps,P=(m.flags&128)!==0;if(m.flags&=-129,h===null){if(wn){if(S.mode==="hidden")return h=Iy(m,S),m.lanes=536870912,Hg(null,h);if(a2(m),(h=lr)?(h=I5(h,Os),h=h!==null&&h.data==="&"?h:null,h!==null&&(m.memoizedState={dehydrated:h,treeContext:Qc!==null?{id:Wo,overflow:qo}:null,retryLane:536870912,hydrationErrors:null},_=l4(h),_.return=m,m.child=_,Hi=m,lr=null)):h=null,h===null)throw eu(m);return m.lanes=536870912,null}return Iy(m,S)}var D=h.memoizedState;if(D!==null){var X=D.dehydrated;if(a2(m),P)if(m.flags&256)m.flags&=-257,m=EI(h,m,_);else if(m.memoizedState!==null)m.child=h.child,m.flags|=128,m=null;else throw Error(r(558));else if(Xr||Pf(h,m,_,!1),P=(_&h.childLanes)!==0,Xr||P){if(S=er,S!==null&&(X=Mi(S,_),X!==0&&X!==D.retryLane))throw D.retryLane=X,Id(h,X),Ba(S,h,X),A2;Ky(),m=EI(h,m,_)}else h=D.treeContext,lr=Is(X.nextSibling),Hi=m,wn=!0,Jc=null,Os=!1,h!==null&&d4(m,h),m=Iy(m,S),m.flags|=4096;return m}return h=Rl(h.child,{mode:S.mode,children:S.children}),h.ref=m.ref,m.child=h,h.return=m,h}function Fy(h,m){var _=m.ref;if(_===null)h!==null&&h.ref!==null&&(m.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(h===null||h.ref!==_)&&(m.flags|=4194816)}}function T2(h,m,_,S,P){return Bd(m),_=o2(h,m,_,S,void 0,P),S=l2(),h!==null&&!Xr?(c2(h,m,P),Fl(h,m,P)):(wn&&S&&HE(m),m.flags|=1,Vi(h,m,_,P),m.child)}function AI(h,m,_,S,P,D){return Bd(m),m.updateQueue=null,_=N4(m,S,_,P),C4(h),S=l2(),h!==null&&!Xr?(c2(h,m,D),Fl(h,m,D)):(wn&&S&&HE(m),m.flags|=1,Vi(h,m,_,D),m.child)}function TI(h,m,_,S,P){if(Bd(m),m.stateNode===null){var D=Tf,X=_.contextType;typeof X=="object"&&X!==null&&(D=$i(X)),D=new _(S,D),m.memoizedState=D.state!==null&&D.state!==void 0?D.state:null,D.updater=M2,m.stateNode=D,D._reactInternals=m,D=m.stateNode,D.props=S,D.state=m.memoizedState,D.refs={},JE(m),X=_.contextType,D.context=typeof X=="object"&&X!==null?$i(X):Tf,D.state=m.memoizedState,X=_.getDerivedStateFromProps,typeof X=="function"&&(S2(m,_,X,S),D.state=m.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof D.getSnapshotBeforeUpdate=="function"||typeof D.UNSAFE_componentWillMount!="function"&&typeof D.componentWillMount!="function"||(X=D.state,typeof D.componentWillMount=="function"&&D.componentWillMount(),typeof D.UNSAFE_componentWillMount=="function"&&D.UNSAFE_componentWillMount(),X!==D.state&&M2.enqueueReplaceState(D,D.state,null),Ig(m,S,D,P),Lg(),D.state=m.memoizedState),typeof D.componentDidMount=="function"&&(m.flags|=4194308),S=!0}else if(h===null){D=m.stateNode;var le=m.memoizedProps,Te=qd(_,le);D.props=Te;var Ze=D.context,ct=_.contextType;X=Tf,typeof ct=="object"&&ct!==null&&(X=$i(ct));var ht=_.getDerivedStateFromProps;ct=typeof ht=="function"||typeof D.getSnapshotBeforeUpdate=="function",le=m.pendingProps!==le,ct||typeof D.UNSAFE_componentWillReceiveProps!="function"&&typeof D.componentWillReceiveProps!="function"||(le||Ze!==X)&&fI(m,D,S,X),nu=!1;var Je=m.memoizedState;D.state=Je,Ig(m,S,D,P),Lg(),Ze=m.memoizedState,le||Je!==Ze||nu?(typeof ht=="function"&&(S2(m,_,ht,S),Ze=m.memoizedState),(Te=nu||hI(m,_,Te,S,Je,Ze,X))?(ct||typeof D.UNSAFE_componentWillMount!="function"&&typeof D.componentWillMount!="function"||(typeof D.componentWillMount=="function"&&D.componentWillMount(),typeof D.UNSAFE_componentWillMount=="function"&&D.UNSAFE_componentWillMount()),typeof D.componentDidMount=="function"&&(m.flags|=4194308)):(typeof D.componentDidMount=="function"&&(m.flags|=4194308),m.memoizedProps=S,m.memoizedState=Ze),D.props=S,D.state=Ze,D.context=X,S=Te):(typeof D.componentDidMount=="function"&&(m.flags|=4194308),S=!1)}else{D=m.stateNode,e2(h,m),X=m.memoizedProps,ct=qd(_,X),D.props=ct,ht=m.pendingProps,Je=D.context,Ze=_.contextType,Te=Tf,typeof Ze=="object"&&Ze!==null&&(Te=$i(Ze)),le=_.getDerivedStateFromProps,(Ze=typeof le=="function"||typeof D.getSnapshotBeforeUpdate=="function")||typeof D.UNSAFE_componentWillReceiveProps!="function"&&typeof D.componentWillReceiveProps!="function"||(X!==ht||Je!==Te)&&fI(m,D,S,Te),nu=!1,Je=m.memoizedState,D.state=Je,Ig(m,S,D,P),Lg();var st=m.memoizedState;X!==ht||Je!==st||nu||h!==null&&h.dependencies!==null&&yy(h.dependencies)?(typeof le=="function"&&(S2(m,_,le,S),st=m.memoizedState),(ct=nu||hI(m,_,ct,S,Je,st,Te)||h!==null&&h.dependencies!==null&&yy(h.dependencies))?(Ze||typeof D.UNSAFE_componentWillUpdate!="function"&&typeof D.componentWillUpdate!="function"||(typeof D.componentWillUpdate=="function"&&D.componentWillUpdate(S,st,Te),typeof D.UNSAFE_componentWillUpdate=="function"&&D.UNSAFE_componentWillUpdate(S,st,Te)),typeof D.componentDidUpdate=="function"&&(m.flags|=4),typeof D.getSnapshotBeforeUpdate=="function"&&(m.flags|=1024)):(typeof D.componentDidUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=4),typeof D.getSnapshotBeforeUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=1024),m.memoizedProps=S,m.memoizedState=st),D.props=S,D.state=st,D.context=Te,S=ct):(typeof D.componentDidUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=4),typeof D.getSnapshotBeforeUpdate!="function"||X===h.memoizedProps&&Je===h.memoizedState||(m.flags|=1024),S=!1)}return D=S,Fy(h,m),S=(m.flags&128)!==0,D||S?(D=m.stateNode,_=S&&typeof _.getDerivedStateFromError!="function"?null:D.render(),m.flags|=1,h!==null&&S?(m.child=Gd(m,h.child,null,P),m.child=Gd(m,null,_,P)):Vi(h,m,_,P),m.memoizedState=D.state,h=m.child):h=Fl(h,m,P),h}function CI(h,m,_,S){return Ud(),m.flags|=256,Vi(h,m,_,S),m.child}var C2={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function N2(h){return{baseLanes:h,cachePool:x4()}}function k2(h,m,_){return h=h!==null?h.childLanes&~_:0,m&&(h|=fs),h}function NI(h,m,_){var S=m.pendingProps,P=!1,D=(m.flags&128)!==0,X;if((X=D)||(X=h!==null&&h.memoizedState===null?!1:(Fr.current&2)!==0),X&&(P=!0,m.flags&=-129),X=(m.flags&32)!==0,m.flags&=-33,h===null){if(wn){if(P?au(m):su(),(h=lr)?(h=I5(h,Os),h=h!==null&&h.data!=="&"?h:null,h!==null&&(m.memoizedState={dehydrated:h,treeContext:Qc!==null?{id:Wo,overflow:qo}:null,retryLane:536870912,hydrationErrors:null},_=l4(h),_.return=m,m.child=_,Hi=m,lr=null)):h=null,h===null)throw eu(m);return hA(h)?m.lanes=32:m.lanes=536870912,null}var le=S.children;return S=S.fallback,P?(su(),P=m.mode,le=Uy({mode:"hidden",children:le},P),S=Fd(S,P,_,null),le.return=m,S.return=m,le.sibling=S,m.child=le,S=m.child,S.memoizedState=N2(_),S.childLanes=k2(h,X,_),m.memoizedState=C2,Hg(null,S)):(au(m),P2(m,le))}var Te=h.memoizedState;if(Te!==null&&(le=Te.dehydrated,le!==null)){if(D)m.flags&256?(au(m),m.flags&=-257,m=R2(h,m,_)):m.memoizedState!==null?(su(),m.child=h.child,m.flags|=128,m=null):(su(),le=S.fallback,P=m.mode,S=Uy({mode:"visible",children:S.children},P),le=Fd(le,P,_,null),le.flags|=2,S.return=m,le.return=m,S.sibling=le,m.child=S,Gd(m,h.child,null,_),S=m.child,S.memoizedState=N2(_),S.childLanes=k2(h,X,_),m.memoizedState=C2,m=Hg(null,S));else if(au(m),hA(le)){if(X=le.nextSibling&&le.nextSibling.dataset,X)var Ze=X.dgst;X=Ze,S=Error(r(419)),S.stack="",S.digest=X,kg({value:S,source:null,stack:null}),m=R2(h,m,_)}else if(Xr||Pf(h,m,_,!1),X=(_&h.childLanes)!==0,Xr||X){if(X=er,X!==null&&(S=Mi(X,_),S!==0&&S!==Te.retryLane))throw Te.retryLane=S,Id(h,S),Ba(X,h,S),A2;dA(le)||Ky(),m=R2(h,m,_)}else dA(le)?(m.flags|=192,m.child=h.child,m=null):(h=Te.treeContext,lr=Is(le.nextSibling),Hi=m,wn=!0,Jc=null,Os=!1,h!==null&&d4(m,h),m=P2(m,S.children),m.flags|=4096);return m}return P?(su(),le=S.fallback,P=m.mode,Te=h.child,Ze=Te.sibling,S=Rl(Te,{mode:"hidden",children:S.children}),S.subtreeFlags=Te.subtreeFlags&65011712,Ze!==null?le=Rl(Ze,le):(le=Fd(le,P,_,null),le.flags|=2),le.return=m,S.return=m,S.sibling=le,m.child=S,Hg(null,S),S=m.child,le=h.child.memoizedState,le===null?le=N2(_):(P=le.cachePool,P!==null?(Te=qr._currentValue,P=P.parent!==Te?{parent:Te,pool:Te}:P):P=x4(),le={baseLanes:le.baseLanes|_,cachePool:P}),S.memoizedState=le,S.childLanes=k2(h,X,_),m.memoizedState=C2,Hg(h.child,S)):(au(m),_=h.child,h=_.sibling,_=Rl(_,{mode:"visible",children:S.children}),_.return=m,_.sibling=null,h!==null&&(X=m.deletions,X===null?(m.deletions=[h],m.flags|=16):X.push(h)),m.child=_,m.memoizedState=null,_)}function P2(h,m){return m=Uy({mode:"visible",children:m},h.mode),m.return=h,h.child=m}function Uy(h,m){return h=cs(22,h,null,m),h.lanes=0,h}function R2(h,m,_){return Gd(m,h.child,null,_),h=P2(m,m.pendingProps.children),h.flags|=2,m.memoizedState=null,h}function kI(h,m,_){h.lanes|=m;var S=h.alternate;S!==null&&(S.lanes|=m),qE(h.return,m,_)}function j2(h,m,_,S,P,D){var X=h.memoizedState;X===null?h.memoizedState={isBackwards:m,rendering:null,renderingStartTime:0,last:S,tail:_,tailMode:P,treeForkCount:D}:(X.isBackwards=m,X.rendering=null,X.renderingStartTime=0,X.last=S,X.tail=_,X.tailMode=P,X.treeForkCount=D)}function PI(h,m,_){var S=m.pendingProps,P=S.revealOrder,D=S.tail;S=S.children;var X=Fr.current,le=(X&2)!==0;if(le?(X=X&1|2,m.flags|=128):X&=1,J(Fr,X),Vi(h,m,S,_),S=wn?Ng:0,!le&&h!==null&&(h.flags&128)!==0)e:for(h=m.child;h!==null;){if(h.tag===13)h.memoizedState!==null&&kI(h,_,m);else if(h.tag===19)kI(h,_,m);else if(h.child!==null){h.child.return=h,h=h.child;continue}if(h===m)break e;for(;h.sibling===null;){if(h.return===null||h.return===m)break e;h=h.return}h.sibling.return=h.return,h=h.sibling}switch(P){case"forwards":for(_=m.child,P=null;_!==null;)h=_.alternate,h!==null&&Ty(h)===null&&(P=_),_=_.sibling;_=P,_===null?(P=m.child,m.child=null):(P=_.sibling,_.sibling=null),j2(m,!1,P,_,D,S);break;case"backwards":case"unstable_legacy-backwards":for(_=null,P=m.child,m.child=null;P!==null;){if(h=P.alternate,h!==null&&Ty(h)===null){m.child=P;break}h=P.sibling,P.sibling=_,_=P,P=h}j2(m,!0,_,null,D,S);break;case"together":j2(m,!1,null,null,void 0,S);break;default:m.memoizedState=null}return m.child}function Fl(h,m,_){if(h!==null&&(m.dependencies=h.dependencies),cu|=m.lanes,(_&m.childLanes)===0)if(h!==null){if(Pf(h,m,_,!1),(_&m.childLanes)===0)return null}else return null;if(h!==null&&m.child!==h.child)throw Error(r(153));if(m.child!==null){for(h=m.child,_=Rl(h,h.pendingProps),m.child=_,_.return=m;h.sibling!==null;)h=h.sibling,_=_.sibling=Rl(h,h.pendingProps),_.return=m;_.sibling=null}return m.child}function D2(h,m){return(h.lanes&m)!==0?!0:(h=h.dependencies,!!(h!==null&&yy(h)))}function kne(h,m,_){switch(m.tag){case 3:Re(m,m.stateNode.containerInfo),tu(m,qr,h.memoizedState.cache),Ud();break;case 27:case 5:we(m);break;case 4:Re(m,m.stateNode.containerInfo);break;case 10:tu(m,m.type,m.memoizedProps.value);break;case 31:if(m.memoizedState!==null)return m.flags|=128,a2(m),null;break;case 13:var S=m.memoizedState;if(S!==null)return S.dehydrated!==null?(au(m),m.flags|=128,null):(_&m.child.childLanes)!==0?NI(h,m,_):(au(m),h=Fl(h,m,_),h!==null?h.sibling:null);au(m);break;case 19:var P=(h.flags&128)!==0;if(S=(_&m.childLanes)!==0,S||(Pf(h,m,_,!1),S=(_&m.childLanes)!==0),P){if(S)return PI(h,m,_);m.flags|=128}if(P=m.memoizedState,P!==null&&(P.rendering=null,P.tail=null,P.lastEffect=null),J(Fr,Fr.current),S)break;return null;case 22:return m.lanes=0,SI(h,m,_,m.pendingProps);case 24:tu(m,qr,h.memoizedState.cache)}return Fl(h,m,_)}function RI(h,m,_){if(h!==null)if(h.memoizedProps!==m.pendingProps)Xr=!0;else{if(!D2(h,_)&&(m.flags&128)===0)return Xr=!1,kne(h,m,_);Xr=(h.flags&131072)!==0}else Xr=!1,wn&&(m.flags&1048576)!==0&&u4(m,Ng,m.index);switch(m.lanes=0,m.tag){case 16:e:{var S=m.pendingProps;if(h=$d(m.elementType),m.type=h,typeof h=="function")UE(h)?(S=qd(h,S),m.tag=1,m=TI(null,m,h,S,_)):(m.tag=0,m=T2(null,m,h,S,_));else{if(h!=null){var P=h.$$typeof;if(P===A){m.tag=11,m=vI(null,m,h,S,_);break e}else if(P===C){m.tag=14,m=_I(null,m,h,S,_);break e}}throw m=R(h)||h,Error(r(306,m,""))}}return m;case 0:return T2(h,m,m.type,m.pendingProps,_);case 1:return S=m.type,P=qd(S,m.pendingProps),TI(h,m,S,P,_);case 3:e:{if(Re(m,m.stateNode.containerInfo),h===null)throw Error(r(387));S=m.pendingProps;var D=m.memoizedState;P=D.element,e2(h,m),Ig(m,S,null,_);var X=m.memoizedState;if(S=X.cache,tu(m,qr,S),S!==D.cache&&KE(m,[qr],_,!0),Lg(),S=X.element,D.isDehydrated)if(D={element:S,isDehydrated:!1,cache:X.cache},m.updateQueue.baseState=D,m.memoizedState=D,m.flags&256){m=CI(h,m,S,_);break e}else if(S!==P){P=Rs(Error(r(424)),m),kg(P),m=CI(h,m,S,_);break e}else{switch(h=m.stateNode.containerInfo,h.nodeType){case 9:h=h.body;break;default:h=h.nodeName==="HTML"?h.ownerDocument.body:h}for(lr=Is(h.firstChild),Hi=m,wn=!0,Jc=null,Os=!0,_=S4(m,null,S,_),m.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(Ud(),S===P){m=Fl(h,m,_);break e}Vi(h,m,S,_)}m=m.child}return m;case 26:return Fy(h,m),h===null?(_=$5(m.type,null,m.pendingProps,null))?m.memoizedState=_:wn||(_=m.type,h=m.pendingProps,S=tv(fe.current).createElement(_),S[yr]=m,S[Lr]=h,Gi(S,_,h),Le(S),m.stateNode=S):m.memoizedState=$5(m.type,h.memoizedProps,m.pendingProps,h.memoizedState),null;case 27:return we(m),h===null&&wn&&(S=m.stateNode=z5(m.type,m.pendingProps,fe.current),Hi=m,Os=!0,P=lr,mu(m.type)?(fA=P,lr=Is(S.firstChild)):lr=P),Vi(h,m,m.pendingProps.children,_),Fy(h,m),h===null&&(m.flags|=4194304),m.child;case 5:return h===null&&wn&&((P=S=lr)&&(S=sre(S,m.type,m.pendingProps,Os),S!==null?(m.stateNode=S,Hi=m,lr=Is(S.firstChild),Os=!1,P=!0):P=!1),P||eu(m)),we(m),P=m.type,D=m.pendingProps,X=h!==null?h.memoizedProps:null,S=D.children,lA(P,D)?S=null:X!==null&&lA(P,X)&&(m.flags|=32),m.memoizedState!==null&&(P=o2(h,m,_ne,null,null,_),rx._currentValue=P),Fy(h,m),Vi(h,m,S,_),m.child;case 6:return h===null&&wn&&((h=_=lr)&&(_=ore(_,m.pendingProps,Os),_!==null?(m.stateNode=_,Hi=m,lr=null,h=!0):h=!1),h||eu(m)),null;case 13:return NI(h,m,_);case 4:return Re(m,m.stateNode.containerInfo),S=m.pendingProps,h===null?m.child=Gd(m,null,S,_):Vi(h,m,S,_),m.child;case 11:return vI(h,m,m.type,m.pendingProps,_);case 7:return Vi(h,m,m.pendingProps,_),m.child;case 8:return Vi(h,m,m.pendingProps.children,_),m.child;case 12:return Vi(h,m,m.pendingProps.children,_),m.child;case 10:return S=m.pendingProps,tu(m,m.type,S.value),Vi(h,m,S.children,_),m.child;case 9:return P=m.type._context,S=m.pendingProps.children,Bd(m),P=$i(P),S=S(P),m.flags|=1,Vi(h,m,S,_),m.child;case 14:return _I(h,m,m.type,m.pendingProps,_);case 15:return wI(h,m,m.type,m.pendingProps,_);case 19:return PI(h,m,_);case 31:return Nne(h,m,_);case 22:return SI(h,m,_,m.pendingProps);case 24:return Bd(m),S=$i(qr),h===null?(P=ZE(),P===null&&(P=er,D=XE(),P.pooledCache=D,D.refCount++,D!==null&&(P.pooledCacheLanes|=_),P=D),m.memoizedState={parent:S,cache:P},JE(m),tu(m,qr,P)):((h.lanes&_)!==0&&(e2(h,m),Ig(m,null,null,_),Lg()),P=h.memoizedState,D=m.memoizedState,P.parent!==S?(P={parent:S,cache:S},m.memoizedState=P,m.lanes===0&&(m.memoizedState=m.updateQueue.baseState=P),tu(m,qr,S)):(S=D.cache,tu(m,qr,S),S!==P.cache&&KE(m,[qr],_,!0))),Vi(h,m,m.pendingProps.children,_),m.child;case 29:throw m.pendingProps}throw Error(r(156,m.tag))}function Ul(h){h.flags|=4}function O2(h,m,_,S,P){if((m=(h.mode&32)!==0)&&(m=!1),m){if(h.flags|=16777216,(P&335544128)===P)if(h.stateNode.complete)h.flags|=8192;else if(a5())h.flags|=8192;else throw Vd=Sy,QE}else h.flags&=-16777217}function jI(h,m){if(m.type!=="stylesheet"||(m.state.loading&4)!==0)h.flags&=-16777217;else if(h.flags|=16777216,!K5(m))if(a5())h.flags|=8192;else throw Vd=Sy,QE}function zy(h,m){m!==null&&(h.flags|=4),h.flags&16384&&(m=h.tag!==22?xt():536870912,h.lanes|=m,$f|=m)}function $g(h,m){if(!wn)switch(h.tailMode){case"hidden":m=h.tail;for(var _=null;m!==null;)m.alternate!==null&&(_=m),m=m.sibling;_===null?h.tail=null:_.sibling=null;break;case"collapsed":_=h.tail;for(var S=null;_!==null;)_.alternate!==null&&(S=_),_=_.sibling;S===null?m||h.tail===null?h.tail=null:h.tail.sibling=null:S.sibling=null}}function cr(h){var m=h.alternate!==null&&h.alternate.child===h.child,_=0,S=0;if(m)for(var P=h.child;P!==null;)_|=P.lanes|P.childLanes,S|=P.subtreeFlags&65011712,S|=P.flags&65011712,P.return=h,P=P.sibling;else for(P=h.child;P!==null;)_|=P.lanes|P.childLanes,S|=P.subtreeFlags,S|=P.flags,P.return=h,P=P.sibling;return h.subtreeFlags|=S,h.childLanes=_,m}function Pne(h,m,_){var S=m.pendingProps;switch($E(m),m.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return cr(m),null;case 1:return cr(m),null;case 3:return _=m.stateNode,S=null,h!==null&&(S=h.memoizedState.cache),m.memoizedState.cache!==S&&(m.flags|=2048),Ol(qr),re(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(h===null||h.child===null)&&(kf(m)?Ul(m):h===null||h.memoizedState.isDehydrated&&(m.flags&256)===0||(m.flags|=1024,GE())),cr(m),null;case 26:var P=m.type,D=m.memoizedState;return h===null?(Ul(m),D!==null?(cr(m),jI(m,D)):(cr(m),O2(m,P,null,S,_))):D?D!==h.memoizedState?(Ul(m),cr(m),jI(m,D)):(cr(m),m.flags&=-16777217):(h=h.memoizedProps,h!==S&&Ul(m),cr(m),O2(m,P,h,S,_)),null;case 27:if(Oe(m),_=fe.current,P=m.type,h!==null&&m.stateNode!=null)h.memoizedProps!==S&&Ul(m);else{if(!S){if(m.stateNode===null)throw Error(r(166));return cr(m),null}h=ie.current,kf(m)?h4(m):(h=z5(P,S,_),m.stateNode=h,Ul(m))}return cr(m),null;case 5:if(Oe(m),P=m.type,h!==null&&m.stateNode!=null)h.memoizedProps!==S&&Ul(m);else{if(!S){if(m.stateNode===null)throw Error(r(166));return cr(m),null}if(D=ie.current,kf(m))h4(m);else{var X=tv(fe.current);switch(D){case 1:D=X.createElementNS("http://www.w3.org/2000/svg",P);break;case 2:D=X.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;default:switch(P){case"svg":D=X.createElementNS("http://www.w3.org/2000/svg",P);break;case"math":D=X.createElementNS("http://www.w3.org/1998/Math/MathML",P);break;case"script":D=X.createElement("div"),D.innerHTML=" - + +