diff --git a/.gitignore b/.gitignore index 024b01e20..bada766f2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ archive/ *.log logs/ *.log* +bambutrack.log.* diff --git a/PLAN.md b/PLAN.md index 6d7cdedd2..27cb52dd9 100644 --- a/PLAN.md +++ b/PLAN.md @@ -58,7 +58,7 @@ Variables use `{variable_name}` syntax (Python format strings). ### Common Variables (all events): - `{timestamp}` - Current date/time -- `{app_name}` - "BambuTrack" +- `{app_name}` - "Bambuddy" --- @@ -104,7 +104,7 @@ maintenance_due: body: "{printer}:\n{items}" test: - title: "BambuTrack Test" + title: "Bambuddy Test" body: "This is a test notification. If you see this, notifications are working!" ``` diff --git a/README.md b/README.md index 8f05d5055..24e230ca3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ TESTERS NEEDED!!! Since I only have X1C and H2D devices, I'm not able to test the application with other Bambu Lab models. Collaborate today and help the project to support the whole Bambu Lab printer family!

- Bambusy Logo + Bambuddy Logo

@@ -188,7 +188,7 @@ Since I only have X1C and H2D devices, I'm not able to test the application with ### Network Requirements - Bambu Lab printer with **LAN Mode** enabled -- Printer and Bambusy server must be on the same local network +- Printer and Bambuddy server must be on the same local network - Ports used: 8883 (MQTT/TLS), 990 (FTPS) ### Supported Printers @@ -203,8 +203,8 @@ Since I only have X1C and H2D devices, I'm not able to test the application with ```bash # Clone the repository -git clone https://github.com/maziggy/bambusy.git -cd bambusy +git clone https://github.com/maziggy/bambuddy.git +cd bambuddy # Create and activate virtual environment python3 -m venv venv @@ -246,8 +246,8 @@ sudo apt install python3 python3-venv python3-pip nodejs npm git #### Step 2: Clone the Repository ```bash -git clone https://github.com/maziggy/bambusy.git -cd bambusy +git clone https://github.com/maziggy/bambuddy.git +cd bambuddy ``` #### Step 3: Set Up Python Environment @@ -300,22 +300,22 @@ Open http://localhost:8000 in your browser. Create a systemd service for automatic startup: ```bash -sudo nano /etc/systemd/system/bambusy.service +sudo nano /etc/systemd/system/bambuddy.service ``` Add the following content (adjust paths as needed): ```ini [Unit] -Description=Bambusy Print Archive +Description=Bambuddy Print Archive After=network.target [Service] Type=simple User=YOUR_USERNAME -WorkingDirectory=/home/YOUR_USERNAME/bambusy -Environment="PATH=/home/YOUR_USERNAME/bambusy/venv/bin" -ExecStart=/home/YOUR_USERNAME/bambusy/venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port 8000 +WorkingDirectory=/home/YOUR_USERNAME/bambuddy +Environment="PATH=/home/YOUR_USERNAME/bambuddy/venv/bin" +ExecStart=/home/YOUR_USERNAME/bambuddy/venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port 8000 Restart=always RestartSec=10 @@ -327,31 +327,31 @@ Enable and start the service: ```bash sudo systemctl daemon-reload -sudo systemctl enable bambusy -sudo systemctl start bambusy +sudo systemctl enable bambuddy +sudo systemctl start bambuddy # Check status -sudo systemctl status bambusy +sudo systemctl status bambuddy # View logs -sudo journalctl -u bambusy -f +sudo journalctl -u bambuddy -f ``` ### Running with Docker (Coming Soon) ```bash docker run -d \ - --name bambusy \ + --name bambuddy \ -p 8000:8000 \ - -v bambusy_data:/app/data \ - -v bambusy_archive:/app/archive \ - maziggy/bambusy:latest + -v bambuddy_data:/app/data \ + -v bambuddy_archive:/app/archive \ + maziggy/bambuddy:latest ``` -### Updating Bambusy +### Updating Bambuddy ```bash -cd bambusy +cd bambuddy git pull origin main # Activate virtual environment @@ -374,7 +374,7 @@ cd .. ### Enabling LAN Mode on Your Printer -To connect Bambusy to your printer, you need to enable LAN Mode: +To connect Bambuddy to your printer, you need to enable LAN Mode: 1. On your printer, go to **Settings** > **Network** > **LAN Mode** 2. Enable **LAN Mode** (this requires Developer Mode to be enabled first) @@ -382,7 +382,7 @@ To connect Bambusy to your printer, you need to enable LAN Mode: 4. Find your printer's **IP Address** in network settings 5. Find your printer's **Serial Number** in device info -### Adding a Printer in Bambusy +### Adding a Printer in Bambuddy 1. Go to the **Printers** page 2. Click **Add Printer** @@ -397,7 +397,7 @@ The printer should connect automatically and show real-time status. ### Environment Variables -Bambusy can be configured using environment variables or a `.env` file in the project root. Copy `.env.example` to `.env` and adjust as needed: +Bambuddy can be configured using environment variables or a `.env` file in the project root. Copy `.env.example` to `.env` and adjust as needed: ```bash cp .env.example .env @@ -407,12 +407,12 @@ cp .env.example .env |----------|---------|-------------| | `DEBUG` | `false` | Enable debug mode (verbose logging, SQL queries) | | `LOG_LEVEL` | `INFO` | Log level when DEBUG=false (`DEBUG`, `INFO`, `WARNING`, `ERROR`) | -| `LOG_TO_FILE` | `true` | Write logs to `logs/bambutrack.log` | +| `LOG_TO_FILE` | `true` | Write logs to `logs/bambuddy.log` | **Production (default):** - INFO level logging - SQLAlchemy and HTTP library noise suppressed -- Logs written to `logs/bambutrack.log` (5MB rotating, 3 backups) +- Logs written to `logs/bambuddy.log` (5MB rotating, 3 backups) **Development (`DEBUG=true`):** - DEBUG level logging (verbose) @@ -499,7 +499,7 @@ When a scheduled print is ready to start: ### K-Profiles (Pressure Advance) -K-profiles store pressure advance (Linear Advance) settings for different filament and nozzle combinations. Bambusy lets you view and manage these settings directly on your printers. +K-profiles store pressure advance (Linear Advance) settings for different filament and nozzle combinations. Bambuddy lets you view and manage these settings directly on your printers. #### Viewing K-Profiles @@ -513,7 +513,7 @@ K-profiles store pressure advance (Linear Advance) settings for different filame #### Dual-Nozzle Printers (H2 Series) -For dual-nozzle printers (H2D, H2C, H2S), Bambusy automatically detects the nozzle configuration and displays: +For dual-nozzle printers (H2D, H2C, H2S), Bambuddy automatically detects the nozzle configuration and displays: - **Left/Right columns** showing profiles for each extruder - **Extruder filter** to show profiles for one extruder only - **Extruder selector** when adding new profiles @@ -535,7 +535,7 @@ The nozzle count is auto-detected from MQTT temperature data when the printer co 4. For dual-nozzle printers, select Left or Right extruder 5. Enter the K-value and click **Save** -**Note:** Filaments must first be calibrated in Bambu Studio to appear in the dropdown. Bambusy reads the filament list from existing K-profiles on the printer. +**Note:** Filaments must first be calibrated in Bambu Studio to appear in the dropdown. Bambuddy reads the filament list from existing K-profiles on the printer. #### Filtering and Search @@ -545,7 +545,7 @@ The nozzle count is auto-detected from MQTT temperature data when the printer co ### Smart Plug Integration -Bambusy supports Tasmota-based smart plugs for automated power control. This is useful for: +Bambuddy supports Tasmota-based smart plugs for automated power control. This is useful for: - Automatically turning on your printer when a print starts - Safely turning off the printer after it cools down - Energy savings by powering off idle printers @@ -597,7 +597,7 @@ Use cases: #### Power Monitoring & Alerts -For Tasmota plugs with energy monitoring (e.g., Sonoff S31), Bambusy can alert you when power consumption exceeds a threshold: +For Tasmota plugs with energy monitoring (e.g., Sonoff S31), Bambuddy can alert you when power consumption exceeds a threshold: 1. Enable **Power Alert** in the plug settings 2. Set the **Power Threshold** in watts (e.g., 200W) @@ -618,7 +618,7 @@ Each plug card shows: ### Push Notifications -Bambusy can send push notifications when print events occur. Notifications are useful for monitoring prints remotely without checking the app constantly. +Bambuddy can send push notifications when print events occur. Notifications are useful for monitoring prints remotely without checking the app constantly. #### Supported Providers @@ -713,7 +713,7 @@ Common variables available for all events: `{timestamp}`, `{app_name}` ### Spoolman Integration -Bambusy integrates with [Spoolman](https://github.com/Donkie/Spoolman) for filament inventory management. When enabled, AMS filament data syncs with your Spoolman server, allowing you to track remaining filament across all your spools. +Bambuddy integrates with [Spoolman](https://github.com/Donkie/Spoolman) for filament inventory management. When enabled, AMS filament data syncs with your Spoolman server, allowing you to track remaining filament across all your spools. #### Prerequisites @@ -745,7 +745,7 @@ When connected: #### How Syncing Works -Bambusy matches AMS spools to Spoolman spools using the **tray UUID** - a unique 32-character identifier that Bambu Lab assigns to each original spool. This ensures consistent matching across different printer models. +Bambuddy matches AMS spools to Spoolman spools using the **tray UUID** - a unique 32-character identifier that Bambu Lab assigns to each original spool. This ensures consistent matching across different printer models. **What gets synced:** - Remaining filament weight (from AMS sensor) @@ -768,7 +768,7 @@ Bambusy matches AMS spools to Spoolman spools using the **tray UUID** - a unique - This is normal behavior - they don't have Bambu Lab tray UUIDs **Connection issues:** -- Verify the Spoolman URL is accessible from your Bambusy server +- Verify the Spoolman URL is accessible from your Bambuddy server - Check that no firewall is blocking port 7912 (or your custom port) - Ensure Spoolman is running and healthy @@ -778,22 +778,22 @@ Bambusy matches AMS spools to Spoolman spools using the **tray UUID** - a unique 1. Add CallMeBot to your contacts: +34 644 51 95 23 2. Send "I allow callmebot to send me messages" via WhatsApp 3. You'll receive an API key -4. Enter your phone number (with country code) and API key in Bambusy +4. Enter your phone number (with country code) and API key in Bambuddy **ntfy:** 1. Choose a unique topic name (e.g., `my-printer-alerts-xyz123`) 2. Subscribe to it on your phone using the ntfy app or web interface -3. Enter the topic name in Bambusy (server defaults to ntfy.sh) +3. Enter the topic name in Bambuddy (server defaults to ntfy.sh) **Pushover:** 1. Create an account at [pushover.net](https://pushover.net/) 2. Create an application to get an API token -3. Enter your user key and app token in Bambusy +3. Enter your user key and app token in Bambuddy **Telegram:** 1. Message @BotFather on Telegram to create a bot 2. Get your chat ID by messaging @userinfobot -3. Enter the bot token and chat ID in Bambusy +3. Enter the bot token and chat ID in Bambuddy **Email:** 1. Configure your SMTP server settings @@ -805,12 +805,12 @@ Bambusy matches AMS spools to Spoolman spools using the **tray UUID** - a unique 1. In your Discord server, go to channel settings > Integrations > Webhooks 2. Click "New Webhook" and customize the name/avatar if desired 3. Copy the webhook URL -4. Paste the webhook URL in Bambusy +4. Paste the webhook URL in Bambuddy **Webhook (Generic):** 1. Enter any URL that accepts POST requests 2. Optionally add custom headers (e.g., Authorization tokens) -3. Bambusy sends JSON payloads with event details +3. Bambuddy sends JSON payloads with event details 4. Useful for integrating with custom systems, Home Assistant, IFTTT, etc. ## Tech Stack @@ -824,7 +824,7 @@ Bambusy matches AMS spools to Spoolman spools using the **tray UUID** - a unique ## Project Structure ``` -bambusy/ +bambuddy/ ├── backend/ │ └── app/ │ ├── api/routes/ # API endpoints @@ -835,7 +835,7 @@ bambusy/ ├── frontend/ # React application ├── static/ # Built frontend + images ├── archive/ # Stored 3MF files -└── bambusy.db # SQLite database +└── bambuddy.db # SQLite database ``` ## API Documentation @@ -884,27 +884,27 @@ Contributions are welcome! Please feel free to submit a Pull Request. ### Database errors -The SQLite database (`bambusy.db`) is created automatically. If you encounter issues: +The SQLite database (`bambuddy.db`) is created automatically. If you encounter issues: ```bash # Backup and reset database -mv bambusy.db bambusy.db.backup +mv bambuddy.db bambuddy.db.backup # Restart the application - a new database will be created ``` ### View server logs -Bambusy writes logs to `bambutrack.log` in the application directory (rotating, max 5MB × 3 files). +Bambuddy writes logs to `bambuddy.log` in the application directory (rotating, max 5MB × 3 files). ```bash # View live log file -tail -f bambutrack.log +tail -f bambuddy.log # If running directly with verbose output uvicorn backend.app.main:app --host 0.0.0.0 --port 8000 --log-level debug # If running as systemd service -sudo journalctl -u bambusy -f +sudo journalctl -u bambuddy -f ``` ### Smart plug not responding @@ -913,13 +913,13 @@ sudo journalctl -u bambusy -f 2. **Test via browser** - Visit `http:///cm?cmnd=Power` to test directly 3. **Check Tasmota web interface** - Access `http://` to verify Tasmota is running 4. **Authentication** - If Tasmota has a password set, configure it in the plug settings -5. **Firewall** - Ensure port 80 is accessible between Bambusy server and the plug +5. **Firewall** - Ensure port 80 is accessible between Bambuddy server and the plug ### Auto power-off not working 1. **Check plug is linked** - The plug must be linked to a printer for automation 2. **Verify automation is enabled** - Check the Enabled, Auto On, and Auto Off toggles -3. **Temperature mode issues** - If using temperature mode, ensure the printer is still connected so Bambusy can read the nozzle temperature +3. **Temperature mode issues** - If using temperature mode, ensure the printer is still connected so Bambuddy can read the nozzle temperature ### Scheduled print not starting @@ -927,7 +927,7 @@ sudo journalctl -u bambusy -f 2. **Verify smart plug** - If using auto power-on, ensure the smart plug is configured and working 3. **Check queue status** - Look at the queue page for error messages 4. **Time zone issues** - Scheduled times are in your local time zone; ensure your system clock is correct -5. **View logs** - Check `bambutrack.log` for detailed error messages +5. **View logs** - Check `bambuddy.log` for detailed error messages ### Print queue shows "Failed to start" @@ -939,7 +939,7 @@ Common causes: ### Timelapse not attaching automatically **The Problem:** -When printers run in **LAN-only mode** (disconnected from Bambu Cloud), they cannot sync time via NTP. This causes the printer's internal clock to drift significantly (sometimes days or weeks off). Bambusy matches timelapses by comparing the print completion time with the timelapse file's modification time - when the printer's clock is wrong, this matching fails. +When printers run in **LAN-only mode** (disconnected from Bambu Cloud), they cannot sync time via NTP. This causes the printer's internal clock to drift significantly (sometimes days or weeks off). Bambuddy matches timelapses by comparing the print completion time with the timelapse file's modification time - when the printer's clock is wrong, this matching fails. **Symptoms:** - "Scan for Timelapse" shows "No matching timelapse found" @@ -947,7 +947,7 @@ When printers run in **LAN-only mode** (disconnected from Bambu Cloud), they can - Printer shows incorrect date/time in its settings **Workaround - Manual Selection:** -When automatic matching fails, Bambusy now offers manual timelapse selection: +When automatic matching fails, Bambuddy now offers manual timelapse selection: 1. Right-click the archive and select **"Scan for Timelapse"** 2. If no match is found, a dialog appears showing all available timelapse files on the printer diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index 0aded5596..359cf716b 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -1,15 +1,25 @@ +import io import json +import zipfile from datetime import datetime +from pathlib import Path +from typing import Optional -from fastapi import APIRouter, Depends, UploadFile, File -from fastapi.responses import JSONResponse +from fastapi import APIRouter, Depends, UploadFile, File, Query +from fastapi.responses import JSONResponse, StreamingResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select +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.schemas.settings import AppSettings, AppSettingsUpdate @@ -149,62 +159,267 @@ async def update_spoolman_settings( @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", +async def export_backup( + db: AsyncSession = Depends(get_db), + include_settings: bool = Query(True, description="Include app settings"), + include_notifications: bool = Query(True, description="Include notification providers"), + include_templates: bool = Query(True, description="Include notification templates"), + include_smart_plugs: bool = Query(True, description="Include smart plugs"), + include_printers: bool = Query(False, description="Include printers (without access codes)"), + 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"), +): + """Export selected data as JSON backup.""" + backup: dict = { + "version": "2.0", "exported_at": datetime.utcnow().isoformat(), - "settings": settings_data, - "notification_providers": providers_data, - "smart_plugs": plugs_data, + "included": [], } + # Settings + if include_settings: + result = await db.execute(select(Settings)) + db_settings = result.scalars().all() + backup["settings"] = {s.key: s.value for s in db_settings} + backup["included"].append("settings") + + # Notification providers + if include_notifications: + result = await db.execute(select(NotificationProvider)) + 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["included"].append("notification_providers") + + # Notification templates + if include_templates: + result = await db.execute(select(NotificationTemplate)) + 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["included"].append("notification_templates") + + # Smart plugs + if include_smart_plugs: + result = await db.execute(select(SmartPlug)) + 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["included"].append("smart_plugs") + + # Printers (without access codes for security) + if include_printers: + result = await db.execute(select(Printer)) + printers = result.scalars().all() + backup["printers"] = [] + for printer in printers: + backup["printers"].append({ + "name": printer.name, + "serial_number": printer.serial_number, + "ip_address": printer.ip_address, + # access_code intentionally excluded for security + "model": printer.model, + "location": printer.location, + "nozzle_count": printer.nozzle_count, + "is_active": printer.is_active, + "auto_archive": printer.auto_archive, + "print_hours_offset": printer.print_hours_offset, + }) + backup["included"].append("printers") + + # Filaments + if include_filaments: + result = await db.execute(select(Filament)) + 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["included"].append("filaments") + + # Maintenance types and records + if include_maintenance: + # Maintenance types + result = await db.execute(select(MaintenanceType)) + 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["included"].append("maintenance_types") + + # Print archives with file paths for ZIP + archive_files: list[tuple[str, Path]] = [] # (zip_path, local_path) + if include_archives: + result = await db.execute(select(PrintArchive)) + archives = result.scalars().all() + backup["archives"] = [] + base_dir = app_settings.base_dir + + for a in archives: + archive_data = { + "filename": a.filename, + "file_size": a.file_size, + "content_hash": a.content_hash, + "print_name": a.print_name, + "print_time_seconds": a.print_time_seconds, + "filament_used_grams": a.filament_used_grams, + "filament_type": a.filament_type, + "filament_color": a.filament_color, + "layer_height": a.layer_height, + "total_layers": a.total_layers, + "nozzle_diameter": a.nozzle_diameter, + "bed_temperature": a.bed_temperature, + "nozzle_temperature": a.nozzle_temperature, + "status": a.status, + "started_at": a.started_at.isoformat() if a.started_at else None, + "completed_at": a.completed_at.isoformat() if a.completed_at else None, + "makerworld_url": a.makerworld_url, + "designer": a.designer, + "is_favorite": a.is_favorite, + "tags": a.tags, + "notes": a.notes, + "cost": a.cost, + "failure_reason": a.failure_reason, + "energy_kwh": a.energy_kwh, + "energy_cost": a.energy_cost, + "extra_data": a.extra_data, + "photos": a.photos, + } + + # Collect file paths for ZIP + if a.file_path: + file_path = base_dir / a.file_path + if file_path.exists(): + archive_data["file_path"] = a.file_path + archive_files.append((a.file_path, file_path)) + + if a.thumbnail_path: + thumb_path = base_dir / a.thumbnail_path + if thumb_path.exists(): + archive_data["thumbnail_path"] = a.thumbnail_path + archive_files.append((a.thumbnail_path, thumb_path)) + + if a.timelapse_path: + timelapse_path = base_dir / a.timelapse_path + if timelapse_path.exists(): + archive_data["timelapse_path"] = a.timelapse_path + archive_files.append((a.timelapse_path, timelapse_path)) + + if a.source_3mf_path: + source_path = base_dir / a.source_3mf_path + if source_path.exists(): + archive_data["source_3mf_path"] = a.source_3mf_path + archive_files.append((a.source_3mf_path, source_path)) + + # Include photos + if a.photos: + for photo in a.photos: + photo_path = base_dir / "archive" / "photos" / photo + if photo_path.exists(): + zip_photo_path = f"archive/photos/{photo}" + archive_files.append((zip_photo_path, photo_path)) + + backup["archives"].append(archive_data) + backup["included"].append("archives") + + # If archives included, create ZIP file with all files + if include_archives and archive_files: + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: + # Add backup.json + zf.writestr("backup.json", json.dumps(backup, indent=2)) + + # Add all archive files + added_files = set() + for zip_path, local_path in archive_files: + if zip_path not in added_files and local_path.exists(): + try: + zf.write(local_path, zip_path) + added_files.add(zip_path) + except Exception: + pass # Skip files that can't be read + + zip_buffer.seek(0) + filename = f"bambuddy-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.zip" + return StreamingResponse( + zip_buffer, + media_type="application/zip", + headers={"Content-Disposition": f"attachment; filename={filename}"} + ) + + # Otherwise return JSON return JSONResponse( content=backup, headers={ - "Content-Disposition": f"attachment; filename=bambutrack-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json" + "Content-Disposition": f"attachment; filename=bambuddy-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json" } ) @@ -214,14 +429,54 @@ async def import_backup( file: UploadFile = File(...), db: AsyncSession = Depends(get_db), ): - """Restore settings, notification providers, and smart plugs from JSON backup.""" + """Restore data from JSON or ZIP backup. Skips duplicates.""" try: content = await file.read() - backup = json.loads(content.decode("utf-8")) + base_dir = app_settings.base_dir + files_restored = 0 + + # Check if it's a ZIP file + if file.filename and file.filename.endswith('.zip'): + try: + zip_buffer = io.BytesIO(content) + with zipfile.ZipFile(zip_buffer, 'r') as zf: + # Extract backup.json + if 'backup.json' not in zf.namelist(): + return {"success": False, "message": "Invalid ZIP: missing 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': + continue + # Ensure path is safe (no path traversal) + 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: + dst.write(src.read()) + files_restored += 1 + except zipfile.BadZipFile: + return {"success": False, "message": "Invalid ZIP file"} + else: + backup = json.loads(content.decode("utf-8")) + except json.JSONDecodeError as e: + return {"success": False, "message": f"Invalid JSON: {str(e)}"} except Exception as e: return {"success": False, "message": f"Invalid backup file: {str(e)}"} - restored = {"settings": 0, "notification_providers": 0, "smart_plugs": 0} + restored = { + "settings": 0, + "notification_providers": 0, + "notification_templates": 0, + "smart_plugs": 0, + "printers": 0, + "filaments": 0, + "maintenance_types": 0, + } # Restore settings if "settings" in backup: @@ -232,7 +487,6 @@ async def import_backup( # 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"]) ) @@ -255,14 +509,42 @@ async def import_backup( 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"), + daily_digest_enabled=provider_data.get("daily_digest_enabled", False), + daily_digest_time=provider_data.get("daily_digest_time"), + printer_id=provider_data.get("printer_id"), ) db.add(provider) restored["notification_providers"] += 1 + # Restore notification templates (update existing by event_type) + 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"] + ) + ) + existing = result.scalar_one_or_none() + if existing: + # Update existing template + existing.name = template_data.get("name", existing.name) + existing.title_template = template_data.get("title_template", existing.title_template) + existing.body_template = template_data.get("body_template", existing.body_template) + existing.is_default = template_data.get("is_default", False) + else: + template = NotificationTemplate( + event_type=template_data["event_type"], + name=template_data["name"], + title_template=template_data["title_template"], + body_template=template_data["body_template"], + is_default=template_data.get("is_default", False), + ) + db.add(template) + restored["notification_templates"] += 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"]) ) @@ -271,17 +553,161 @@ async def import_backup( plug = SmartPlug( name=plug_data["name"], ip_address=plug_data["ip_address"], + printer_id=plug_data.get("printer_id"), 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), + auto_on=plug_data.get("auto_on", True), + auto_off=plug_data.get("auto_off", True), + off_delay_mode=plug_data.get("off_delay_mode", "time"), + off_delay_minutes=plug_data.get("off_delay_minutes", 5), + off_temp_threshold=plug_data.get("off_temp_threshold", 70), + username=plug_data.get("username"), + password=plug_data.get("password"), + power_alert_enabled=plug_data.get("power_alert_enabled", False), + power_alert_high=plug_data.get("power_alert_high"), + power_alert_low=plug_data.get("power_alert_low"), + schedule_enabled=plug_data.get("schedule_enabled", False), + schedule_on_time=plug_data.get("schedule_on_time"), + schedule_off_time=plug_data.get("schedule_off_time"), ) db.add(plug) restored["smart_plugs"] += 1 + # Restore printers (skip duplicates by serial_number, requires access_code to be set manually) + if "printers" in backup: + for printer_data in backup["printers"]: + result = await db.execute( + select(Printer).where(Printer.serial_number == printer_data["serial_number"]) + ) + existing = result.scalar_one_or_none() + if not existing: + printer = Printer( + name=printer_data["name"], + serial_number=printer_data["serial_number"], + ip_address=printer_data["ip_address"], + access_code="CHANGE_ME", # Must be set manually for security + model=printer_data.get("model"), + location=printer_data.get("location"), + nozzle_count=printer_data.get("nozzle_count", 1), + is_active=False, # Disabled until access_code is set + auto_archive=printer_data.get("auto_archive", True), + print_hours_offset=printer_data.get("print_hours_offset", 0.0), + ) + db.add(printer) + restored["printers"] += 1 + + # Restore filaments (skip duplicates by name+type+brand) + if "filaments" in backup: + for filament_data in backup["filaments"]: + result = await db.execute( + select(Filament).where( + Filament.name == filament_data["name"], + Filament.type == filament_data["type"], + Filament.brand == filament_data.get("brand"), + ) + ) + existing = result.scalar_one_or_none() + if not existing: + filament = Filament( + name=filament_data["name"], + type=filament_data["type"], + brand=filament_data.get("brand"), + color=filament_data.get("color"), + color_hex=filament_data.get("color_hex"), + cost_per_kg=filament_data.get("cost_per_kg", 25.0), + spool_weight_g=filament_data.get("spool_weight_g", 1000.0), + currency=filament_data.get("currency", "USD"), + density=filament_data.get("density"), + print_temp_min=filament_data.get("print_temp_min"), + print_temp_max=filament_data.get("print_temp_max"), + bed_temp_min=filament_data.get("bed_temp_min"), + bed_temp_max=filament_data.get("bed_temp_max"), + ) + db.add(filament) + restored["filaments"] += 1 + + # Restore maintenance types (skip 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"]) + ) + existing = result.scalar_one_or_none() + if not existing: + mt = MaintenanceType( + name=mt_data["name"], + description=mt_data.get("description"), + default_interval_hours=mt_data.get("default_interval_hours", 100.0), + interval_type=mt_data.get("interval_type", "hours"), + icon=mt_data.get("icon"), + is_system=mt_data.get("is_system", False), + ) + db.add(mt) + restored["maintenance_types"] += 1 + + # Restore archives (skip duplicates by content_hash) + if "archives" in backup: + for archive_data in backup["archives"]: + # 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) + ) + existing = result.scalar_one_or_none() + if existing: + continue + + # Only restore if file exists (from ZIP extraction) + file_path = archive_data.get("file_path") + if file_path and (base_dir / file_path).exists(): + archive = PrintArchive( + filename=archive_data["filename"], + file_path=file_path, + file_size=archive_data.get("file_size", 0), + content_hash=content_hash, + thumbnail_path=archive_data.get("thumbnail_path"), + timelapse_path=archive_data.get("timelapse_path"), + source_3mf_path=archive_data.get("source_3mf_path"), + print_name=archive_data.get("print_name"), + print_time_seconds=archive_data.get("print_time_seconds"), + filament_used_grams=archive_data.get("filament_used_grams"), + filament_type=archive_data.get("filament_type"), + filament_color=archive_data.get("filament_color"), + layer_height=archive_data.get("layer_height"), + total_layers=archive_data.get("total_layers"), + nozzle_diameter=archive_data.get("nozzle_diameter"), + bed_temperature=archive_data.get("bed_temperature"), + nozzle_temperature=archive_data.get("nozzle_temperature"), + status=archive_data.get("status", "completed"), + makerworld_url=archive_data.get("makerworld_url"), + designer=archive_data.get("designer"), + is_favorite=archive_data.get("is_favorite", False), + tags=archive_data.get("tags"), + notes=archive_data.get("notes"), + cost=archive_data.get("cost"), + failure_reason=archive_data.get("failure_reason"), + energy_kwh=archive_data.get("energy_kwh"), + energy_cost=archive_data.get("energy_cost"), + extra_data=archive_data.get("extra_data"), + photos=archive_data.get("photos"), + ) + db.add(archive) + restored["archives"] = restored.get("archives", 0) + 1 + await db.commit() + # Build summary message + parts = [] + for key, count in restored.items(): + if count > 0: + parts.append(f"{count} {key.replace('_', ' ')}") + + if files_restored > 0: + parts.append(f"{files_restored} files") + return { "success": True, - "message": f"Restored {restored['settings']} settings, {restored['notification_providers']} notification providers, {restored['smart_plugs']} smart plugs", + "message": f"Restored: {', '.join(parts)}" if parts else "Nothing to restore", "restored": restored, + "files_restored": files_restored, } diff --git a/backend/app/api/routes/updates.py b/backend/app/api/routes/updates.py index 7077bca13..eaa060873 100644 --- a/backend/app/api/routes/updates.py +++ b/backend/app/api/routes/updates.py @@ -2,7 +2,8 @@ import asyncio import logging -import subprocess +import os +import shutil import sys from pathlib import Path @@ -27,6 +28,30 @@ _update_status = { } +def _find_executable(name: str) -> str | None: + """Find an executable in PATH or common locations.""" + # Try standard PATH first + path = shutil.which(name) + if path: + return path + + # Common locations for executables (useful when running as systemd service) + common_paths = [ + f"/usr/bin/{name}", + f"/usr/local/bin/{name}", + f"/opt/homebrew/bin/{name}", + f"/home/linuxbrew/.linuxbrew/bin/{name}", + f"{os.path.expanduser('~')}/.nvm/current/bin/{name}", + f"{os.path.expanduser('~')}/.local/bin/{name}", + ] + + for p in common_paths: + if os.path.isfile(p) and os.access(p, os.X_OK): + return p + + return None + + def parse_version(version: str) -> tuple[int, ...]: """Parse version string into tuple for comparison.""" # Remove 'v' prefix if present @@ -140,21 +165,55 @@ async def check_for_updates(db: AsyncSession = Depends(get_db)): async def _perform_update(): - """Perform the actual update using git pull.""" + """Perform the actual update using git fetch and reset.""" global _update_status try: + base_dir = settings.base_dir + + # Find git executable (may not be in PATH when running as systemd service) + git_path = _find_executable("git") + if not git_path: + _update_status = { + "status": "error", + "progress": 0, + "message": "Git not found", + "error": "Could not find git executable. Please ensure git is installed.", + } + return + + logger.info(f"Using git at: {git_path}") + + # Git config to avoid safe.directory issues + git_config = ["-c", f"safe.directory={base_dir}"] + _update_status = { "status": "downloading", - "progress": 20, - "message": "Pulling latest changes...", + "progress": 10, + "message": "Configuring git...", "error": None, } - # Run git pull in the project directory - base_dir = settings.base_dir + # Ensure remote uses HTTPS (SSH may not be available) + https_url = f"https://github.com/{GITHUB_REPO}.git" process = await asyncio.create_subprocess_exec( - "git", "pull", "--rebase", + git_path, *git_config, "remote", "set-url", "origin", https_url, + cwd=str(base_dir), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + await process.communicate() + + _update_status = { + "status": "downloading", + "progress": 20, + "message": "Fetching latest changes...", + "error": None, + } + + # Fetch from origin + process = await asyncio.create_subprocess_exec( + git_path, *git_config, "fetch", "origin", "main", cwd=str(base_dir), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -162,12 +221,39 @@ async def _perform_update(): stdout, stderr = await process.communicate() if process.returncode != 0: - error_msg = stderr.decode() if stderr else "Git pull failed" - logger.error(f"Git pull failed: {error_msg}") + error_msg = stderr.decode() if stderr else "Git fetch failed" + logger.error(f"Git fetch failed: {error_msg}") _update_status = { "status": "error", "progress": 0, - "message": "Failed to pull updates", + "message": "Failed to fetch updates", + "error": error_msg, + } + return + + _update_status = { + "status": "downloading", + "progress": 40, + "message": "Applying updates...", + "error": None, + } + + # Hard reset to origin/main (clean update, no merge conflicts) + process = await asyncio.create_subprocess_exec( + git_path, *git_config, "reset", "--hard", "origin/main", + cwd=str(base_dir), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await process.communicate() + + if process.returncode != 0: + error_msg = stderr.decode() if stderr else "Git reset failed" + logger.error(f"Git reset failed: {error_msg}") + _update_status = { + "status": "error", + "progress": 0, + "message": "Failed to apply updates", "error": error_msg, } return @@ -191,19 +277,21 @@ async def _perform_update(): if process.returncode != 0: logger.warning(f"pip install warning: {stderr.decode() if stderr else 'unknown'}") - _update_status = { - "status": "installing", - "progress": 70, - "message": "Building frontend...", - "error": None, - } - - # Build frontend + # Try to build frontend if npm is available (optional - static files are pre-built) + npm_path = _find_executable("npm") frontend_dir = base_dir / "frontend" - if frontend_dir.exists(): + + if npm_path and frontend_dir.exists(): + _update_status = { + "status": "installing", + "progress": 70, + "message": "Building frontend...", + "error": None, + } + # npm install process = await asyncio.create_subprocess_exec( - "npm", "install", + npm_path, "install", cwd=str(frontend_dir), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -212,7 +300,7 @@ async def _perform_update(): # npm run build process = await asyncio.create_subprocess_exec( - "npm", "run", "build", + npm_path, "run", "build", cwd=str(frontend_dir), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -221,6 +309,8 @@ async def _perform_update(): if process.returncode != 0: logger.warning(f"Frontend build warning: {stderr.decode() if stderr else 'unknown'}") + else: + logger.info("npm not found or frontend dir missing - using pre-built static files") _update_status = { "status": "complete", diff --git a/backend/app/core/config.py b/backend/app/core/config.py index c6f77633c..2fc9f9578 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,21 +1,49 @@ from pathlib import Path from pydantic_settings import BaseSettings +import logging # Application version - single source of truth APP_VERSION = "0.1.5b" -GITHUB_REPO = "maziggy/bambusy" +GITHUB_REPO = "maziggy/bambuddy" + +# Base directory for path calculations +_base_dir = Path(__file__).resolve().parent.parent.parent.parent + + +def _migrate_database() -> Path: + """Migrate database from old name to new name if needed.""" + old_db = _base_dir / "bambutrack.db" + new_db = _base_dir / "bambuddy.db" + + # If old database exists and new one doesn't, rename it + if old_db.exists() and not new_db.exists(): + try: + old_db.rename(new_db) + logging.info(f"Migrated database: {old_db} -> {new_db}") + except Exception as e: + logging.warning(f"Could not migrate database: {e}. Using old location.") + return old_db + + # If old database exists (and new one now exists too), it was migrated + # If only new exists, use new + # If neither exists, use new (will be created) + return new_db if new_db.exists() or not old_db.exists() else old_db + + +# Determine database path (handles migration) +_db_path = _migrate_database() class Settings(BaseSettings): - app_name: str = "BambuTrack" + app_name: str = "Bambuddy" debug: bool = False # Default to production mode # Paths - base_dir: Path = Path(__file__).resolve().parent.parent.parent.parent + base_dir: Path = _base_dir archive_dir: Path = base_dir / "archive" static_dir: Path = base_dir / "static" log_dir: Path = base_dir / "logs" - database_url: str = f"sqlite+aiosqlite:///{base_dir}/bambutrack.db" + database_url: str = f"sqlite+aiosqlite:///{_db_path}" # Logging log_level: str = "INFO" # Override with LOG_LEVEL env var or DEBUG=true diff --git a/backend/app/i18n/__init__.py b/backend/app/i18n/__init__.py index 580662a72..454536897 100644 --- a/backend/app/i18n/__init__.py +++ b/backend/app/i18n/__init__.py @@ -33,8 +33,8 @@ EN = { "soon": "Soon", # Test notification - "test_title": "BambuTrack Test", - "test_message": "This is a test notification from BambuTrack. If you see this, notifications are working correctly!", + "test_title": "Bambuddy Test", + "test_message": "This is a test notification from Bambuddy. If you see this, notifications are working correctly!", } } @@ -69,8 +69,8 @@ DE = { "soon": "Bald", # Test notification - "test_title": "BambuTrack Test", - "test_message": "Dies ist eine Testbenachrichtigung von BambuTrack. Wenn Sie dies sehen, funktionieren die Benachrichtigungen!", + "test_title": "Bambuddy Test", + "test_message": "Dies ist eine Testbenachrichtigung von Bambuddy. Wenn Sie dies sehen, funktionieren die Benachrichtigungen!", } } diff --git a/backend/app/main.py b/backend/app/main.py index f2379b14b..fcedabc79 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -29,7 +29,7 @@ root_logger.addHandler(console_handler) # File handler - only in production or if explicitly enabled if app_settings.log_to_file: - log_file = app_settings.log_dir / "bambutrack.log" + log_file = app_settings.log_dir / "bambuddy.log" file_handler = RotatingFileHandler( log_file, maxBytes=5*1024*1024, # 5MB @@ -47,7 +47,7 @@ if not app_settings.debug: logging.getLogger("httpcore").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) -logging.info(f"BambuTrack starting - debug={app_settings.debug}, log_level={log_level_str}") +logging.info(f"Bambuddy starting - debug={app_settings.debug}, log_level={log_level_str}") from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse @@ -1062,7 +1062,7 @@ async def serve_frontend(): if index_file.exists(): return FileResponse(index_file) return { - "message": "BambuTrack API", + "message": "Bambuddy API", "docs": "/docs", "frontend": "Build and place React app in /static directory", } diff --git a/backend/app/models/notification_template.py b/backend/app/models/notification_template.py index f485ee4d0..27543715f 100644 --- a/backend/app/models/notification_template.py +++ b/backend/app/models/notification_template.py @@ -84,7 +84,7 @@ DEFAULT_TEMPLATES = [ { "event_type": "test", "name": "Test Notification", - "title_template": "BambuTrack Test", + "title_template": "Bambuddy Test", "body_template": "This is a test notification. If you see this, notifications are working!", }, ] diff --git a/backend/app/schemas/notification_template.py b/backend/app/schemas/notification_template.py index 52af8bf67..cd6c5b3a3 100644 --- a/backend/app/schemas/notification_template.py +++ b/backend/app/schemas/notification_template.py @@ -42,7 +42,7 @@ SAMPLE_DATA: dict[str, dict[str, str]] = { "filename": "Benchy.3mf", "estimated_time": "1h 23m", "timestamp": "2024-01-15 14:30", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "print_complete": { "printer": "Bambu X1C", @@ -50,7 +50,7 @@ SAMPLE_DATA: dict[str, dict[str, str]] = { "duration": "1h 18m", "filament_grams": "15.2", "timestamp": "2024-01-15 15:48", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "print_failed": { "printer": "Bambu X1C", @@ -58,14 +58,14 @@ SAMPLE_DATA: dict[str, dict[str, str]] = { "duration": "0h 45m", "reason": "Filament runout", "timestamp": "2024-01-15 15:15", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "print_stopped": { "printer": "Bambu X1C", "filename": "Benchy.3mf", "duration": "0h 30m", "timestamp": "2024-01-15 15:00", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "print_progress": { "printer": "Bambu X1C", @@ -73,19 +73,19 @@ SAMPLE_DATA: dict[str, dict[str, str]] = { "progress": "50", "remaining_time": "0h 41m", "timestamp": "2024-01-15 15:00", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "printer_offline": { "printer": "Bambu X1C", "timestamp": "2024-01-15 14:30", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "printer_error": { "printer": "Bambu X1C", "error_type": "AMS Error", "error_detail": "Filament slot 1 jammed", "timestamp": "2024-01-15 14:30", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "filament_low": { "printer": "Bambu X1C", @@ -93,16 +93,16 @@ SAMPLE_DATA: dict[str, dict[str, str]] = { "remaining_percent": "15", "color": "Black PLA", "timestamp": "2024-01-15 14:30", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "maintenance_due": { "printer": "Bambu X1C", "items": "• Nozzle cleaning (OVERDUE)\n• Carbon rod lubrication (Soon)", "timestamp": "2024-01-15 14:30", - "app_name": "BambuTrack", + "app_name": "Bambuddy", }, "test": { - "app_name": "BambuTrack", + "app_name": "Bambuddy", "timestamp": "2024-01-15 14:30", }, } diff --git a/backend/app/services/bambu_cloud.py b/backend/app/services/bambu_cloud.py index b6f383ed9..cf9911ba3 100644 --- a/backend/app/services/bambu_cloud.py +++ b/backend/app/services/bambu_cloud.py @@ -50,7 +50,7 @@ class BambuCloudService: """Get headers for authenticated requests.""" headers = { "Content-Type": "application/json", - "User-Agent": "BambuTrack/1.0", + "User-Agent": "Bambuddy/1.0", } if self.access_token: headers["Authorization"] = f"Bearer {self.access_token}" diff --git a/backend/app/services/bambu_mqtt.py b/backend/app/services/bambu_mqtt.py index bab0636ae..309610546 100644 --- a/backend/app/services/bambu_mqtt.py +++ b/backend/app/services/bambu_mqtt.py @@ -278,6 +278,27 @@ class BambuMQTTClient: def topic_publish(self) -> str: return f"device/{self.serial_number}/request" + # Maximum time (seconds) without a message before considering connection stale + STALE_TIMEOUT = 60.0 + + def is_stale(self) -> bool: + """Check if the connection is stale (no messages for too long).""" + if self._last_message_time == 0: + return False # Never received a message yet + time_since_last = time.time() - self._last_message_time + return time_since_last > self.STALE_TIMEOUT + + def check_staleness(self) -> bool: + """Check staleness and update connected state if stale. Returns True if connected.""" + if self.state.connected and self.is_stale(): + logger.warning( + f"[{self.serial_number}] Connection stale - no message for {time.time() - self._last_message_time:.1f}s" + ) + self.state.connected = False + if self.on_state_change: + self.on_state_change(self.state) + return self.state.connected + def _on_connect(self, client, userdata, flags, rc, properties=None): if rc == 0: self.state.connected = True @@ -1481,7 +1502,7 @@ class BambuMQTTClient: self._loop = loop self._client = mqtt.Client( callback_api_version=mqtt.CallbackAPIVersion.VERSION2, - client_id=f"bambutrack_{self.serial_number}", + client_id=f"bambuddy_{self.serial_number}", protocol=mqtt.MQTTv311, ) diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py index 6d0cf2fd4..87eedd88c 100644 --- a/backend/app/services/notification_service.py +++ b/backend/app/services/notification_service.py @@ -119,7 +119,7 @@ class NotificationService: """Build notification title and body from template.""" # Add common variables variables["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M") - variables["app_name"] = "BambuTrack" + variables["app_name"] = "Bambuddy" template = await self._get_template(db, event_type) if not template: @@ -139,7 +139,7 @@ class NotificationService: if db: title, message = await self._build_message_from_template(db, "test", {}) else: - title = "BambuTrack Test" + title = "Bambuddy Test" message = "This is a test notification. If you see this, notifications are working!" try: @@ -287,7 +287,7 @@ class NotificationService: msg = MIMEMultipart() msg["From"] = from_email msg["To"] = to_email - msg["Subject"] = f"[BambuTrack] {subject}" + msg["Subject"] = f"[Bambuddy] {subject}" msg.attach(MIMEText(body, "plain")) if security == "ssl": @@ -357,7 +357,7 @@ class NotificationService: custom_field_title: title, custom_field_message: message, "timestamp": datetime.now().isoformat(), - "source": "BambuTrack", + "source": "Bambuddy", } headers = {"Content-Type": "application/json"} diff --git a/backend/app/services/printer_manager.py b/backend/app/services/printer_manager.py index fa5948e42..e570fb791 100644 --- a/backend/app/services/printer_manager.py +++ b/backend/app/services/printer_manager.py @@ -106,22 +106,29 @@ class PrinterManager: self.disconnect_printer(printer_id) def get_status(self, printer_id: int) -> PrinterState | None: - """Get the current status of a printer.""" + """Get the current status of a printer (checks for stale connections).""" if printer_id in self._clients: - return self._clients[printer_id].state + client = self._clients[printer_id] + # Check staleness and update connected state if needed + client.check_staleness() + return client.state return None def get_all_statuses(self) -> dict[int, PrinterState]: - """Get status of all connected printers.""" - return { - printer_id: client.state - for printer_id, client in self._clients.items() - } + """Get status of all connected printers (checks for stale connections).""" + result = {} + for printer_id, client in self._clients.items(): + # Check staleness and update connected state if needed + client.check_staleness() + result[printer_id] = client.state + return result def is_connected(self, printer_id: int) -> bool: - """Check if a printer is connected.""" + """Check if a printer is connected (checks for stale connections).""" if printer_id in self._clients: - return self._clients[printer_id].state.connected + client = self._clients[printer_id] + # Check staleness and update connected state if needed + return client.check_staleness() return False def get_client(self, printer_id: int) -> BambuMQTTClient | None: diff --git a/frontend/index.html b/frontend/index.html index 7f4bed61f..8727e069a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - Bambusy + Bambuddy diff --git a/frontend/public/img/android-chrome-192x192.png b/frontend/public/img/android-chrome-192x192.png index a394aed35..b0a45c6c0 100644 Binary files a/frontend/public/img/android-chrome-192x192.png and b/frontend/public/img/android-chrome-192x192.png differ diff --git a/frontend/public/img/android-chrome-512x512.png b/frontend/public/img/android-chrome-512x512.png index 1f3509f6d..5f871b422 100644 Binary files a/frontend/public/img/android-chrome-512x512.png and b/frontend/public/img/android-chrome-512x512.png differ diff --git a/frontend/public/img/apple-touch-icon.png b/frontend/public/img/apple-touch-icon.png index 69e136c42..63cb2e2f6 100644 Binary files a/frontend/public/img/apple-touch-icon.png and b/frontend/public/img/apple-touch-icon.png differ diff --git a/frontend/public/img/bambuddy_logo_dark.png b/frontend/public/img/bambuddy_logo_dark.png new file mode 100644 index 000000000..125103460 Binary files /dev/null and b/frontend/public/img/bambuddy_logo_dark.png differ diff --git a/frontend/public/img/bambuddy_logo_light.png b/frontend/public/img/bambuddy_logo_light.png new file mode 100644 index 000000000..a123aa63d Binary files /dev/null and b/frontend/public/img/bambuddy_logo_light.png differ diff --git a/frontend/public/img/bambusy_logo_dark.png b/frontend/public/img/bambusy_logo_dark.png deleted file mode 100644 index d026dd6ff..000000000 Binary files a/frontend/public/img/bambusy_logo_dark.png and /dev/null differ diff --git a/frontend/public/img/bambusy_logo_light.png b/frontend/public/img/bambusy_logo_light.png deleted file mode 100644 index 13311f9e0..000000000 Binary files a/frontend/public/img/bambusy_logo_light.png and /dev/null differ diff --git a/frontend/public/img/favicon-16x16.png b/frontend/public/img/favicon-16x16.png index df56b4740..1b016c05b 100644 Binary files a/frontend/public/img/favicon-16x16.png and b/frontend/public/img/favicon-16x16.png differ diff --git a/frontend/public/img/favicon-32x32.png b/frontend/public/img/favicon-32x32.png index a727e0122..daf516eae 100644 Binary files a/frontend/public/img/favicon-32x32.png and b/frontend/public/img/favicon-32x32.png differ diff --git a/frontend/public/img/favicon.png b/frontend/public/img/favicon.png index 1f3509f6d..5a513bcaf 100644 Binary files a/frontend/public/img/favicon.png and b/frontend/public/img/favicon.png differ diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 9810b6537..dc80dcc90 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1149,9 +1149,31 @@ export const api = { }), resetSettings: () => request('/settings/reset', { method: 'POST' }), - exportBackup: async () => { - const response = await fetch(`${API_BASE}/settings/backup`); - return response.json(); + exportBackup: async (categories?: Record): Promise<{ blob: Blob; filename: string }> => { + const params = new URLSearchParams(); + if (categories) { + if (categories.settings !== undefined) params.set('include_settings', String(categories.settings)); + 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.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)); + } + const url = `${API_BASE}/settings/backup${params.toString() ? '?' + params.toString() : ''}`; + const response = await fetch(url); + + // Get filename from Content-Disposition header + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = 'bambuddy-backup.json'; + if (contentDisposition) { + const match = contentDisposition.match(/filename=([^;]+)/); + if (match) filename = match[1].trim(); + } + + const blob = await response.blob(); + return { blob, filename }; }, importBackup: async (file: File) => { const formData = new FormData(); diff --git a/frontend/src/components/AddNotificationModal.tsx b/frontend/src/components/AddNotificationModal.tsx index 22a0a8028..401ddc848 100644 --- a/frontend/src/components/AddNotificationModal.tsx +++ b/frontend/src/components/AddNotificationModal.tsx @@ -167,7 +167,7 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal case 'ntfy': return [ { key: 'server', label: 'Server URL', placeholder: 'https://ntfy.sh', type: 'text', required: false }, - { key: 'topic', label: 'Topic', placeholder: 'my-bambutrack', type: 'text', required: true }, + { key: 'topic', label: 'Topic', placeholder: 'my-bambuddy', type: 'text', required: true }, { key: 'auth_token', label: 'Auth Token', placeholder: 'Optional authentication', type: 'password', required: false }, ]; case 'pushover': diff --git a/frontend/src/components/BackupModal.tsx b/frontend/src/components/BackupModal.tsx new file mode 100644 index 000000000..59caadd1f --- /dev/null +++ b/frontend/src/components/BackupModal.tsx @@ -0,0 +1,267 @@ +import { useEffect, useState } from 'react'; +import { Download, X, Settings, Bell, FileText, Plug, Printer, Palette, Wrench, Archive, Loader2 } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Card, CardContent } from './Card'; +import { Button } from './Button'; + +interface BackupCategory { + id: string; + labelKey: string; + defaultLabel: string; + icon: React.ReactNode; + default: boolean; + description: string; +} + +const BACKUP_CATEGORIES: BackupCategory[] = [ + { + id: 'settings', + labelKey: 'backup.categories.settings', + defaultLabel: 'App Settings', + icon: , + default: true, + description: 'Language, theme, update preferences', + }, + { + id: 'notifications', + labelKey: 'backup.categories.notifications', + defaultLabel: 'Notification Providers', + icon: , + default: true, + description: 'ntfy, Pushover, Discord, etc.', + }, + { + id: 'templates', + labelKey: 'backup.categories.templates', + defaultLabel: 'Notification Templates', + icon: , + default: true, + description: 'Custom message templates', + }, + { + id: 'smart_plugs', + labelKey: 'backup.categories.smartPlugs', + defaultLabel: 'Smart Plugs', + icon: , + default: true, + description: 'Tasmota plug configurations', + }, + { + id: 'printers', + labelKey: 'backup.categories.printers', + defaultLabel: 'Printers', + icon: , + default: false, + description: 'Printer info (access codes excluded)', + }, + { + id: 'filaments', + labelKey: 'backup.categories.filaments', + defaultLabel: 'Filament Inventory', + icon: , + default: false, + description: 'Filament types and costs', + }, + { + id: 'maintenance', + labelKey: 'backup.categories.maintenance', + defaultLabel: 'Maintenance Types', + icon: , + default: false, + description: 'Custom maintenance schedules', + }, + { + id: 'archives', + labelKey: 'backup.categories.archives', + defaultLabel: 'Print Archives', + icon: , + default: false, + description: 'All print data + files (3MF, thumbnails, photos)', + }, +]; + +interface BackupModalProps { + onClose: () => void; + onExport: (categories: Record) => Promise; +} + +export function BackupModal({ onClose, onExport }: BackupModalProps) { + const { t } = useTranslation(); + const [selected, setSelected] = useState>(() => { + const initial: Record = {}; + BACKUP_CATEGORIES.forEach((cat) => { + initial[cat.id] = cat.default; + }); + return initial; + }); + const [isExporting, setIsExporting] = useState(false); + + // Close on Escape key + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [onClose]); + + const toggleCategory = (id: string) => { + setSelected((prev) => ({ ...prev, [id]: !prev[id] })); + }; + + const selectAll = () => { + const all: Record = {}; + BACKUP_CATEGORIES.forEach((cat) => { + all[cat.id] = true; + }); + setSelected(all); + }; + + const selectNone = () => { + const none: Record = {}; + BACKUP_CATEGORIES.forEach((cat) => { + none[cat.id] = false; + }); + setSelected(none); + }; + + const selectedCount = Object.values(selected).filter(Boolean).length; + + const handleExport = async () => { + setIsExporting(true); + try { + await onExport(selected); + } finally { + setIsExporting(false); + } + }; + + return ( +

+ e.stopPropagation()}> + + {/* Header */} +
+
+
+ +
+
+

+ {t('backup.exportTitle', { defaultValue: 'Export Backup' })} +

+

+ {t('backup.selectCategories', { defaultValue: 'Select data to include' })} +

+
+
+ +
+ + {/* Quick actions */} +
+ + | + +
+ + {/* Categories */} +
+ {BACKUP_CATEGORIES.map((category) => ( + + ))} +
+ + {/* Archive warning */} + {selected.archives && ( +
+
+ +
+ ZIP file will be created. + Includes all 3MF files, thumbnails, timelapses, and photos. This may take a while and result in a large file. +
+
+
+ )} + + {/* Footer */} +
+ + {t('backup.selectedCount', { + count: selectedCount, + defaultValue: `${selectedCount} categories selected`, + })} + +
+ + +
+
+
+
+
+ ); +} diff --git a/frontend/src/components/KProfilesView.tsx b/frontend/src/components/KProfilesView.tsx index 8e1842f49..2b99e6449 100644 --- a/frontend/src/components/KProfilesView.tsx +++ b/frontend/src/components/KProfilesView.tsx @@ -580,7 +580,7 @@ function KProfileModal({ className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none resize-none" />

- Notes are saved in Bambusy, not on the printer + Notes are saved in Bambuddy, not on the printer

diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 9807f9ca0..81865c7c4 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom'; -import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, type LucideIcon } from 'lucide-react'; +import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, X, type LucideIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useTheme } from '../contexts/ThemeContext'; import { KeyboardShortcutsModal } from './KeyboardShortcutsModal'; @@ -80,6 +80,9 @@ export function Layout() { const [draggedIndex, setDraggedIndex] = useState(null); const [dragOverIndex, setDragOverIndex] = useState(null); const hasRedirected = useRef(false); + const [dismissedUpdateVersion, setDismissedUpdateVersion] = useState(() => + sessionStorage.getItem('dismissedUpdateVersion') + ); // Check for updates const { data: versionInfo } = useQuery({ @@ -102,6 +105,18 @@ export function Layout() { refetchInterval: 60 * 60 * 1000, // Check every hour }); + // Show update banner if update available and not dismissed for this version + const showUpdateBanner = updateCheck?.update_available && + updateCheck.latest_version && + updateCheck.latest_version !== dismissedUpdateVersion; + + const dismissUpdateBanner = () => { + if (updateCheck?.latest_version) { + sessionStorage.setItem('dismissedUpdateVersion', updateCheck.latest_version); + setDismissedUpdateVersion(updateCheck.latest_version); + } + }; + // Redirect to default view on initial load useEffect(() => { if (!hasRedirected.current && location.pathname === '/') { @@ -200,8 +215,8 @@ export function Layout() { {/* Logo */}
Bambusy
@@ -280,7 +295,7 @@ export function Layout() {
)} + {/* Persistent update banner */} + {showUpdateBanner && ( +
+
+ + + {t('nav.updateAvailableBanner', { + version: updateCheck?.latest_version, + defaultValue: `Version ${updateCheck?.latest_version} is available!` + })} + + +
+ +
+ )} diff --git a/frontend/src/pages/CameraPage.tsx b/frontend/src/pages/CameraPage.tsx index 62db3d437..39dc12210 100644 --- a/frontend/src/pages/CameraPage.tsx +++ b/frontend/src/pages/CameraPage.tsx @@ -30,7 +30,7 @@ export function CameraPage() { document.title = `${printer.name} - Camera`; } return () => { - document.title = 'Bambusy'; + document.title = 'Bambuddy'; }; }, [printer]); diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index b4e15de6c..216381a45 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -12,6 +12,7 @@ import { AddNotificationModal } from '../components/AddNotificationModal'; import { NotificationTemplateEditor } from '../components/NotificationTemplateEditor'; import { NotificationLogViewer } from '../components/NotificationLogViewer'; import { ConfirmModal } from '../components/ConfirmModal'; +import { BackupModal } from '../components/BackupModal'; import { SpoolmanSettings } from '../components/SpoolmanSettings'; import { defaultNavItems, getDefaultView, setDefaultView } from '../components/Layout'; import { availableLanguages } from '../i18n'; @@ -37,6 +38,7 @@ export function SettingsPage() { const [showClearLogsConfirm, setShowClearLogsConfirm] = useState(false); const [showClearStorageConfirm, setShowClearStorageConfirm] = useState(false); const [showBulkPlugConfirm, setShowBulkPlugConfirm] = useState<'on' | 'off' | null>(null); + const [showBackupModal, setShowBackupModal] = useState(false); const handleDefaultViewChange = (path: string) => { setDefaultViewState(path); @@ -311,7 +313,7 @@ export function SettingsPage() {

Settings

-

Configure Bambusy

+

Configure Bambuddy

{/* Tab Navigation */} @@ -866,29 +868,15 @@ export function SettingsPage() { {/* Backup/Restore */}
-

Backup Settings

+

Backup Data

- Export settings, providers, and plugs to JSON + Export settings, providers, printers, and more

); } diff --git a/static/assets/index-79rMOukP.css b/static/assets/index-79rMOukP.css new file mode 100644 index 000000000..87877168d --- /dev/null +++ b/static/assets/index-79rMOukP.css @@ -0,0 +1 @@ +@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-200:oklch(94.5% .129 101.54);--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-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--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-\[400px\]{max-height:400px}.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-\[100px\]{min-width:100px}.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-gray{border-color:var(--color-bambu-gray)}.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\/20{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.bg-bambu-dark\/20{background-color:color-mix(in oklab,var(--color-bambu-dark)20%,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-emerald-500\/20{background-color:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/20{background-color:color-mix(in oklab,var(--color-emerald-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-1{padding-top:calc(var(--spacing)*1)}.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-emerald-400{color:var(--color-emerald-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-200{color:var(--color-yellow-200)}.text-yellow-200\/70{color:#fff085b3}@supports (color:color-mix(in lab,red,red)){.text-yellow-200\/70{color:color-mix(in oklab,var(--color-yellow-200)70%,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-dark\/50:hover{background-color:var(--color-bambu-dark)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bambu-dark\/50:hover{background-color:color-mix(in oklab,var(--color-bambu-dark)50%,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-C1C_HwA0.js b/static/assets/index-C1C_HwA0.js new file mode 100644 index 000000000..6dd26f112 --- /dev/null +++ b/static/assets/index-C1C_HwA0.js @@ -0,0 +1,8611 @@ +function Sne(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 Bv=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function to(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var I2={exports:{}},zg={};var pU;function wne(){if(pU)return zg;pU=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 zg.Fragment=e,zg.jsx=n,zg.jsxs=n,zg}var mU;function Mne(){return mU||(mU=1,I2.exports=wne()),I2.exports}var u=Mne(),U2={exports:{}},Zt={};var gU;function Ene(){if(gU)return Zt;gU=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"),d=Symbol.for("react.lazy"),h=Symbol.for("react.activity"),g=Symbol.iterator;function m(B){return B===null||typeof B!="object"?null:(B=g&&B[g]||B["@@iterator"],typeof B=="function"?B:null)}var v={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},y=Object.assign,b={};function x(B,q,ee){this.props=B,this.context=q,this.refs=b,this.updater=ee||v}x.prototype.isReactComponent={},x.prototype.setState=function(B,q){if(typeof B!="object"&&typeof B!="function"&&B!=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,B,q,"setState")},x.prototype.forceUpdate=function(B){this.updater.enqueueForceUpdate(this,B,"forceUpdate")};function M(){}M.prototype=x.prototype;function w(B,q,ee){this.props=B,this.context=q,this.refs=b,this.updater=ee||v}var A=w.prototype=new M;A.constructor=w,y(A,x.prototype),A.isPureReactComponent=!0;var T=Array.isArray;function R(){}var C={H:null,A:null,T:null,S:null},I=Object.prototype.hasOwnProperty;function N(B,q,ee){var ae=ee.ref;return{$$typeof:t,type:B,key:q,ref:ae!==void 0?ae:null,props:ee}}function O(B,q){return N(B.type,q,B.props)}function z(B){return typeof B=="object"&&B!==null&&B.$$typeof===t}function j(B){var q={"=":"=0",":":"=2"};return"$"+B.replace(/[=:]/g,function(ee){return q[ee]})}var W=/\/+/g;function D(B,q){return typeof B=="object"&&B!==null&&B.key!=null?j(""+B.key):q.toString(36)}function $(B){switch(B.status){case"fulfilled":return B.value;case"rejected":throw B.reason;default:switch(typeof B.status=="string"?B.then(R,R):(B.status="pending",B.then(function(q){B.status==="pending"&&(B.status="fulfilled",B.value=q)},function(q){B.status==="pending"&&(B.status="rejected",B.reason=q)})),B.status){case"fulfilled":return B.value;case"rejected":throw B.reason}}throw B}function X(B,q,ee,ae,me){var ie=typeof B;(ie==="undefined"||ie==="boolean")&&(B=null);var pe=!1;if(B===null)pe=!0;else switch(ie){case"bigint":case"string":case"number":pe=!0;break;case"object":switch(B.$$typeof){case t:case e:pe=!0;break;case d:return pe=B._init,X(pe(B._payload),q,ee,ae,me)}}if(pe)return me=me(B),pe=ae===""?"."+D(B,0):ae,T(me)?(ee="",pe!=null&&(ee=pe.replace(W,"$&/")+"/"),X(me,q,ee,"",function(_e){return _e})):me!=null&&(z(me)&&(me=O(me,ee+(me.key==null||B&&B.key===me.key?"":(""+me.key).replace(W,"$&/")+"/")+pe)),q.push(me)),1;pe=0;var Ce=ae===""?".":ae+":";if(T(B))for(var fe=0;fe>>1,te=X[Y];if(0>>1;Yi(ee,Z))aei(me,ee)?(X[Y]=me,X[ae]=Z,Y=ae):(X[Y]=ee,X[q]=Z,Y=q);else if(aei(me,Z))X[Y]=me,X[ae]=Z,Y=ae;else break e}}return V}function i(X,V){var Z=X.sortIndex-V.sortIndex;return Z!==0?Z:X.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=[],d=1,h=null,g=3,m=!1,v=!1,y=!1,b=!1,x=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,w=typeof setImmediate<"u"?setImmediate:null;function A(X){for(var V=n(c);V!==null;){if(V.callback===null)r(c);else if(V.startTime<=X)r(c),V.sortIndex=V.expirationTime,e(l,V);else break;V=n(c)}}function T(X){if(y=!1,A(X),!v)if(n(l)!==null)v=!0,R||(R=!0,j());else{var V=n(c);V!==null&&$(T,V.startTime-X)}}var R=!1,C=-1,I=5,N=-1;function O(){return b?!0:!(t.unstable_now()-NX&&O());){var Y=h.callback;if(typeof Y=="function"){h.callback=null,g=h.priorityLevel;var te=Y(h.expirationTime<=X);if(X=t.unstable_now(),typeof te=="function"){h.callback=te,A(X),V=!0;break t}h===n(l)&&r(l),A(X)}else r(l);h=n(l)}if(h!==null)V=!0;else{var B=n(c);B!==null&&$(T,B.startTime-X),V=!1}}break e}finally{h=null,g=Z,m=!1}V=void 0}}finally{V?j():R=!1}}}var j;if(typeof w=="function")j=function(){w(z)};else if(typeof MessageChannel<"u"){var W=new MessageChannel,D=W.port2;W.port1.onmessage=z,j=function(){D.postMessage(null)}}else j=function(){x(z,0)};function $(X,V){C=x(function(){X(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(X){X.callback=null},t.unstable_forceFrameRate=function(X){0>X||125Y?(X.sortIndex=Z,e(c,X),n(l)===null&&X===n(c)&&(y?(M(C),C=-1):y=!0,$(T,Z-Y))):(X.sortIndex=te,e(l,X),v||m||(v=!0,R||(R=!0,j()))),X},t.unstable_shouldYield=O,t.unstable_wrapCallback=function(X){var V=g;return function(){var Z=g;g=V;try{return X.apply(this,arguments)}finally{g=Z}}}})(B2)),B2}var vU;function Cne(){return vU||(vU=1,z2.exports=Tne()),z2.exports}var H2={exports:{}},Ji={};var yU;function Nne(){if(yU)return Ji;yU=1;var t=Am();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(),H2.exports=Nne(),H2.exports}var SU;function kne(){if(SU)return Bg;SU=1;var t=Cne(),e=Am(),n=NG();function r(f){var p="https://react.dev/errors/"+f;if(1te||(f.current=Y[te],Y[te]=null,te--)}function ee(f,p){te++,Y[te]=f.current,f.current=p}var ae=B(null),me=B(null),ie=B(null),pe=B(null);function Ce(f,p){switch(ee(ie,p),ee(me,f),ee(ae,null),p.nodeType){case 9:case 11:f=(f=p.documentElement)&&(f=f.namespaceURI)?I4(f):0;break;default:if(f=p.tagName,p=p.namespaceURI)p=I4(p),f=U4(p,f);else switch(f){case"svg":f=1;break;case"math":f=2;break;default:f=0}}q(ae),ee(ae,f)}function fe(){q(ae),q(me),q(ie)}function _e(f){f.memoizedState!==null&&ee(pe,f);var p=ae.current,_=U4(p,f.type);p!==_&&(ee(me,f),ee(ae,_))}function Oe(f){me.current===f&&(q(ae),q(me)),pe.current===f&&(q(pe),jg._currentValue=Z)}var ue,at;function H(f){if(ue===void 0)try{throw Error()}catch(_){var p=_.stack.trim().match(/\n( *(at )?)/);ue=p&&p[1]||"",at=-1<_.stack.indexOf(` + at`)?" ()":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return` +`+ue+f+at}var F=!1;function ne(f,p){if(!f||F)return"";F=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var S={DetermineComponentFrameRoot:function(){try{if(p){var dt=function(){throw Error()};if(Object.defineProperty(dt.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(dt,[])}catch(nt){var We=nt}Reflect.construct(f,[],dt)}else{try{dt.call()}catch(nt){We=nt}f.call(dt.prototype)}}else{try{throw Error()}catch(nt){We=nt}(dt=f())&&typeof dt.catch=="function"&&dt.catch(function(){})}}catch(nt){if(nt&&We&&typeof nt.stack=="string")return[nt.stack,We.stack]}return[null,null]}};S.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var k=Object.getOwnPropertyDescriptor(S.DetermineComponentFrameRoot,"name");k&&k.configurable&&Object.defineProperty(S.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var L=S.DetermineComponentFrameRoot(),K=L[0],le=L[1];if(K&&le){var we=K.split(` +`),$e=le.split(` +`);for(k=S=0;Sk||we[S]!==$e[k]){var st=` +`+we[S].replace(" at new "," at ");return f.displayName&&st.includes("")&&(st=st.replace("",f.displayName)),st}while(1<=S&&0<=k);break}}}finally{F=!1,Error.prepareStackTrace=_}return(_=f?f.displayName||f.name:"")?H(_):""}function G(f,p){switch(f.tag){case 26:case 27:case 5:return H(f.type);case 16:return H("Lazy");case 13:return f.child!==p&&p!==null?H("Suspense Fallback"):H("Suspense");case 19:return H("SuspenseList");case 0:case 15:return ne(f.type,!1);case 11:return ne(f.type.render,!1);case 1:return ne(f.type,!0);case 31:return H("Activity");default:return""}}function Q(f){try{var p="",_=null;do p+=G(f,_),_=f,f=f.return;while(f);return p}catch(S){return` +Error generating stack: `+S.message+` +`+S.stack}}var oe=Object.prototype.hasOwnProperty,Ee=t.unstable_scheduleCallback,Ae=t.unstable_cancelCallback,se=t.unstable_shouldYield,U=t.unstable_requestPaint,P=t.unstable_now,J=t.unstable_getCurrentPriorityLevel,ge=t.unstable_ImmediatePriority,ve=t.unstable_UserBlockingPriority,xe=t.unstable_NormalPriority,Ze=t.unstable_LowPriority,De=t.unstable_IdlePriority,Xe=t.log,ot=t.unstable_setDisableYieldValue,Fe=null,ce=null;function Qe(f){if(typeof Xe=="function"&&ot(f),ce&&typeof ce.setStrictMode=="function")try{ce.setStrictMode(Fe,f)}catch{}}var Be=Math.clz32?Math.clz32:de,Ke=Math.log,ut=Math.LN2;function de(f){return f>>>=0,f===0?32:31-(Ke(f)/ut|0)|0}var Je=256,be=262144,Ie=4194304;function Te(f){var p=f&42;if(p!==0)return p;switch(f&-f){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 f&261888;case 262144:case 524288:case 1048576:case 2097152:return f&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return f&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return f}}function ye(f,p,_){var S=f.pendingLanes;if(S===0)return 0;var k=0,L=f.suspendedLanes,K=f.pingedLanes;f=f.warmLanes;var le=S&134217727;return le!==0?(S=le&~L,S!==0?k=Te(S):(K&=le,K!==0?k=Te(K):_||(_=le&~f,_!==0&&(k=Te(_))))):(le=S&~L,le!==0?k=Te(le):K!==0?k=Te(K):_||(_=S&~f,_!==0&&(k=Te(_)))),k===0?0:p!==0&&p!==k&&(p&L)===0&&(L=k&-k,_=p&-p,L>=_||L===32&&(_&4194048)!==0)?p:k}function Ve(f,p){return(f.pendingLanes&~(f.suspendedLanes&~f.pingedLanes)&p)===0}function ft(f,p){switch(f){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 f=Ie;return Ie<<=1,(Ie&62914560)===0&&(Ie=4194304),f}function Rt(f){for(var p=[],_=0;31>_;_++)p.push(f);return p}function cn(f,p){f.pendingLanes|=p,p!==268435456&&(f.suspendedLanes=0,f.pingedLanes=0,f.warmLanes=0)}function Lt(f,p,_,S,k,L){var K=f.pendingLanes;f.pendingLanes=_,f.suspendedLanes=0,f.pingedLanes=0,f.warmLanes=0,f.expiredLanes&=_,f.entangledLanes&=_,f.errorRecoveryDisabledLanes&=_,f.shellSuspendCounter=0;var le=f.entanglements,we=f.expirationTimes,$e=f.hiddenUpdates;for(_=K&~_;0<_;){var st=31-Be(_),dt=1<"u")return null;try{return f.activeElement||f.body}catch{return f.body}}var kr=/[\n"\\]/g;function kn(f){return f.replace(kr,function(p){return"\\"+p.charCodeAt(0).toString(16)+" "})}function io(f,p,_,S,k,L,K,le){f.name="",K!=null&&typeof K!="function"&&typeof K!="symbol"&&typeof K!="boolean"?f.type=K:f.removeAttribute("type"),p!=null?K==="number"?(p===0&&f.value===""||f.value!=p)&&(f.value=""+dn(p)):f.value!==""+dn(p)&&(f.value=""+dn(p)):K!=="submit"&&K!=="reset"||f.removeAttribute("value"),p!=null?yi(f,K,dn(p)):_!=null?yi(f,K,dn(_)):S!=null&&f.removeAttribute("value"),k==null&&L!=null&&(f.defaultChecked=!!L),k!=null&&(f.checked=k&&typeof k!="function"&&typeof k!="symbol"),le!=null&&typeof le!="function"&&typeof le!="symbol"&&typeof le!="boolean"?f.name=""+dn(le):f.removeAttribute("name")}function Pn(f,p,_,S,k,L,K,le){if(L!=null&&typeof L!="function"&&typeof L!="symbol"&&typeof L!="boolean"&&(f.type=L),p!=null||_!=null){if(!(L!=="submit"&&L!=="reset"||p!=null)){Yt(f);return}_=_!=null?""+dn(_):"",p=p!=null?""+dn(p):_,le||p===f.value||(f.value=p),f.defaultValue=p}S=S??k,S=typeof S!="function"&&typeof S!="symbol"&&!!S,f.checked=le?f.checked:!!S,f.defaultChecked=!!S,K!=null&&typeof K!="function"&&typeof K!="symbol"&&typeof K!="boolean"&&(f.name=K),Yt(f)}function yi(f,p,_){p==="number"&&Sr(f.ownerDocument)===f||f.defaultValue===""+_||(f.defaultValue=""+_)}function ni(f,p,_,S){if(f=f.options,p){p={};for(var k=0;k<_.length;k++)p["$"+_[k]]=!0;for(_=0;_"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),IM=!1;if(gl)try{var eg={};Object.defineProperty(eg,"passive",{get:function(){IM=!0}}),window.addEventListener("test",eg,eg),window.removeEventListener("test",eg,eg)}catch{IM=!1}var Uc=null,UM=null,Db=null;function UL(){if(Db)return Db;var f,p=UM,_=p.length,S,k="value"in Uc?Uc.value:Uc.textContent,L=k.length;for(f=0;f<_&&p[f]===k[f];f++);var K=_-f;for(S=1;S<=K&&p[_-S]===k[L-S];S++);return Db=k.slice(f,1=rg),VL=" ",GL=!1;function WL(f,p){switch(f){case"keyup":return Vee.indexOf(p.keyCode)!==-1;case"keydown":return p.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function qL(f){return f=f.detail,typeof f=="object"&&"data"in f?f.data:null}var sh=!1;function Wee(f,p){switch(f){case"compositionend":return qL(p);case"keypress":return p.which!==32?null:(GL=!0,VL);case"textInput":return f=p.data,f===VL&&GL?null:f;default:return null}}function qee(f,p){if(sh)return f==="compositionend"||!$M&&WL(f,p)?(f=UL(),Db=UM=Uc=null,sh=!1,f):null;switch(f){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-f};f=S}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=tj(_)}}function rj(f,p){return f&&p?f===p?!0:f&&f.nodeType===3?!1:p&&p.nodeType===3?rj(f,p.parentNode):"contains"in f?f.contains(p):f.compareDocumentPosition?!!(f.compareDocumentPosition(p)&16):!1:!1}function ij(f){f=f!=null&&f.ownerDocument!=null&&f.ownerDocument.defaultView!=null?f.ownerDocument.defaultView:window;for(var p=Sr(f.document);p instanceof f.HTMLIFrameElement;){try{var _=typeof p.contentWindow.location.href=="string"}catch{_=!1}if(_)f=p.contentWindow;else break;p=Sr(f.document)}return p}function WM(f){var p=f&&f.nodeName&&f.nodeName.toLowerCase();return p&&(p==="input"&&(f.type==="text"||f.type==="search"||f.type==="tel"||f.type==="url"||f.type==="password")||p==="textarea"||f.contentEditable==="true")}var tte=gl&&"documentMode"in document&&11>=document.documentMode,ah=null,qM=null,og=null,XM=!1;function sj(f,p,_){var S=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;XM||ah==null||ah!==Sr(S)||(S=ah,"selectionStart"in S&&WM(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}),og&&ag(og,S)||(og=S,S=Av(qM,"onSelect"),0>=K,k-=K,jo=1<<32-Be(p)+k|_<tn?(bn=Dt,Dt=null):bn=Dt.sibling;var Mn=We(Le,Dt,He[tn],lt);if(Mn===null){Dt===null&&(Dt=bn);break}f&&Dt&&Mn.alternate===null&&p(Le,Dt),Ne=L(Mn,Ne,tn),wn===null?Ft=Mn:wn.sibling=Mn,wn=Mn,Dt=bn}if(tn===He.length)return _(Le,Dt),yn&&bl(Le,tn),Ft;if(Dt===null){for(;tntn?(bn=Dt,Dt=null):bn=Dt.sibling;var au=We(Le,Dt,Mn.value,lt);if(au===null){Dt===null&&(Dt=bn);break}f&&Dt&&au.alternate===null&&p(Le,Dt),Ne=L(au,Ne,tn),wn===null?Ft=au:wn.sibling=au,wn=au,Dt=bn}if(Mn.done)return _(Le,Dt),yn&&bl(Le,tn),Ft;if(Dt===null){for(;!Mn.done;tn++,Mn=He.next())Mn=dt(Le,Mn.value,lt),Mn!==null&&(Ne=L(Mn,Ne,tn),wn===null?Ft=Mn:wn.sibling=Mn,wn=Mn);return yn&&bl(Le,tn),Ft}for(Dt=S(Dt);!Mn.done;tn++,Mn=He.next())Mn=nt(Dt,Le,tn,Mn.value,lt),Mn!==null&&(f&&Mn.alternate!==null&&Dt.delete(Mn.key===null?tn:Mn.key),Ne=L(Mn,Ne,tn),wn===null?Ft=Mn:wn.sibling=Mn,wn=Mn);return f&&Dt.forEach(function(_ne){return p(Le,_ne)}),yn&&bl(Le,tn),Ft}function Fn(Le,Ne,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 Ft=He.key;Ne!==null;){if(Ne.key===Ft){if(Ft=He.type,Ft===y){if(Ne.tag===7){_(Le,Ne.sibling),lt=k(Ne,He.props.children),lt.return=Le,Le=lt;break e}}else if(Ne.elementType===Ft||typeof Ft=="object"&&Ft!==null&&Ft.$$typeof===I&&Td(Ft)===Ne.type){_(Le,Ne.sibling),lt=k(Ne,He.props),hg(lt,He),lt.return=Le,Le=lt;break e}_(Le,Ne);break}else p(Le,Ne);Ne=Ne.sibling}He.type===y?(lt=Sd(He.props.children,Le.mode,lt,He.key),lt.return=Le,Le=lt):(lt=$b(He.type,He.key,He.props,null,Le.mode,lt),hg(lt,He),lt.return=Le,Le=lt)}return K(Le);case v:e:{for(Ft=He.key;Ne!==null;){if(Ne.key===Ft)if(Ne.tag===4&&Ne.stateNode.containerInfo===He.containerInfo&&Ne.stateNode.implementation===He.implementation){_(Le,Ne.sibling),lt=k(Ne,He.children||[]),lt.return=Le,Le=lt;break e}else{_(Le,Ne);break}else p(Le,Ne);Ne=Ne.sibling}lt=tE(He,Le.mode,lt),lt.return=Le,Le=lt}return K(Le);case I:return He=Td(He),Fn(Le,Ne,He,lt)}if($(He))return kt(Le,Ne,He,lt);if(j(He)){if(Ft=j(He),typeof Ft!="function")throw Error(r(150));return He=Ft.call(He),Vt(Le,Ne,He,lt)}if(typeof He.then=="function")return Fn(Le,Ne,Yb(He),lt);if(He.$$typeof===w)return Fn(Le,Ne,Wb(Le,He),lt);Zb(Le,He)}return typeof He=="string"&&He!==""||typeof He=="number"||typeof He=="bigint"?(He=""+He,Ne!==null&&Ne.tag===6?(_(Le,Ne.sibling),lt=k(Ne,He),lt.return=Le,Le=lt):(_(Le,Ne),lt=eE(He,Le.mode,lt),lt.return=Le,Le=lt),K(Le)):_(Le,Ne)}return function(Le,Ne,He,lt){try{fg=0;var Ft=Fn(Le,Ne,He,lt);return xh=null,Ft}catch(Dt){if(Dt===gh||Dt===Xb)throw Dt;var wn=Zs(29,Dt,null,Le.mode);return wn.lanes=lt,wn.return=Le,wn}finally{}}}var Nd=Cj(!0),Nj=Cj(!1),$c=!1;function hE(f){f.updateQueue={baseState:f.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function pE(f,p){f=f.updateQueue,p.updateQueue===f&&(p.updateQueue={baseState:f.baseState,firstBaseUpdate:f.firstBaseUpdate,lastBaseUpdate:f.lastBaseUpdate,shared:f.shared,callbacks:null})}function Vc(f){return{lane:f,tag:0,payload:null,callback:null,next:null}}function Gc(f,p,_){var S=f.updateQueue;if(S===null)return null;if(S=S.shared,(Cn&2)!==0){var k=S.pending;return k===null?p.next=p:(p.next=k.next,k.next=p),S.pending=p,p=Hb(f),fj(f,null,_),p}return Bb(f,S,p,_),Hb(f)}function pg(f,p,_){if(p=p.updateQueue,p!==null&&(p=p.shared,(_&4194048)!==0)){var S=p.lanes;S&=f.pendingLanes,_|=S,p.lanes=_,qn(f,_)}}function mE(f,p){var _=f.updateQueue,S=f.alternate;if(S!==null&&(S=S.updateQueue,_===S)){var k=null,L=null;if(_=_.firstBaseUpdate,_!==null){do{var K={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};L===null?k=L=K:L=L.next=K,_=_.next}while(_!==null);L===null?k=L=p:L=L.next=p}else k=L=p;_={baseState:S.baseState,firstBaseUpdate:k,lastBaseUpdate:L,shared:S.shared,callbacks:S.callbacks},f.updateQueue=_;return}f=_.lastBaseUpdate,f===null?_.firstBaseUpdate=p:f.next=p,_.lastBaseUpdate=p}var gE=!1;function mg(){if(gE){var f=mh;if(f!==null)throw f}}function gg(f,p,_,S){gE=!1;var k=f.updateQueue;$c=!1;var L=k.firstBaseUpdate,K=k.lastBaseUpdate,le=k.shared.pending;if(le!==null){k.shared.pending=null;var we=le,$e=we.next;we.next=null,K===null?L=$e:K.next=$e,K=we;var st=f.alternate;st!==null&&(st=st.updateQueue,le=st.lastBaseUpdate,le!==K&&(le===null?st.firstBaseUpdate=$e:le.next=$e,st.lastBaseUpdate=we))}if(L!==null){var dt=k.baseState;K=0,st=$e=we=null,le=L;do{var We=le.lane&-536870913,nt=We!==le.lane;if(nt?(xn&We)===We:(S&We)===We){We!==0&&We===ph&&(gE=!0),st!==null&&(st=st.next={lane:0,tag:le.tag,payload:le.payload,callback:null,next:null});e:{var kt=f,Vt=le;We=p;var Fn=_;switch(Vt.tag){case 1:if(kt=Vt.payload,typeof kt=="function"){dt=kt.call(Fn,dt,We);break e}dt=kt;break e;case 3:kt.flags=kt.flags&-65537|128;case 0:if(kt=Vt.payload,We=typeof kt=="function"?kt.call(Fn,dt,We):kt,We==null)break e;dt=h({},dt,We);break e;case 2:$c=!0}}We=le.callback,We!==null&&(f.flags|=64,nt&&(f.flags|=8192),nt=k.callbacks,nt===null?k.callbacks=[We]:nt.push(We))}else nt={lane:We,tag:le.tag,payload:le.payload,callback:le.callback,next:null},st===null?($e=st=nt,we=dt):st=st.next=nt,K|=We;if(le=le.next,le===null){if(le=k.shared.pending,le===null)break;nt=le,le=nt.next,nt.next=null,k.lastBaseUpdate=nt,k.shared.pending=null}}while(!0);st===null&&(we=dt),k.baseState=we,k.firstBaseUpdate=$e,k.lastBaseUpdate=st,L===null&&(k.shared.lanes=0),Yc|=K,f.lanes=K,f.memoizedState=dt}}function kj(f,p){if(typeof f!="function")throw Error(r(191,f));f.call(p)}function Rj(f,p){var _=f.callbacks;if(_!==null)for(f.callbacks=null,f=0;f<_.length;f++)kj(_[f],p)}var bh=B(null),Qb=B(0);function Pj(f,p){f=Nl,ee(Qb,f),ee(bh,p),Nl=f|p.baseLanes}function xE(){ee(Qb,Nl),ee(bh,bh.current)}function bE(){Nl=Qb.current,q(bh),q(Qb)}var Qs=B(null),Ea=null;function Wc(f){var p=f.alternate;ee(Rr,Rr.current&1),ee(Qs,f),Ea===null&&(p===null||bh.current!==null||p.memoizedState!==null)&&(Ea=f)}function vE(f){ee(Rr,Rr.current),ee(Qs,f),Ea===null&&(Ea=f)}function Dj(f){f.tag===22?(ee(Rr,Rr.current),ee(Qs,f),Ea===null&&(Ea=f)):qc()}function qc(){ee(Rr,Rr.current),ee(Qs,Qs.current)}function Js(f){q(Qs),Ea===f&&(Ea=null),q(Rr)}var Rr=B(0);function Jb(f){for(var p=f;p!==null;){if(p.tag===13){var _=p.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||E2(_)||A2(_)))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===f)break;for(;p.sibling===null;){if(p.return===null||p.return===f)return null;p=p.return}p.sibling.return=p.return,p=p.sibling}return null}var _l=0,en=null,In=null,Fr=null,ev=!1,vh=!1,kd=!1,tv=0,xg=0,yh=null,dte=0;function wr(){throw Error(r(321))}function yE(f,p){if(p===null)return!1;for(var _=0;_L?L:8;var K=X.T,le={};X.T=le,LE(f,!1,p,_);try{var we=k(),$e=X.S;if($e!==null&&$e(le,we),we!==null&&typeof we=="object"&&typeof we.then=="function"){var st=ute(we,S);vg(f,p,st,na(f))}else vg(f,p,S,na(f))}catch(dt){vg(f,p,{then:function(){},status:"rejected",reason:dt},na())}finally{V.p=L,K!==null&&le.types!==null&&(K.types=le.types),X.T=K}}function gte(){}function DE(f,p,_,S){if(f.tag!==5)throw Error(r(476));var k=cI(f).queue;lI(f,k,p,Z,_===null?gte:function(){return uI(f),_(S)})}function cI(f){var p=f.memoizedState;if(p!==null)return p;p={memoizedState:Z,baseState:Z,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Sl,lastRenderedState:Z},next:null};var _={};return p.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Sl,lastRenderedState:_},next:null},f.memoizedState=p,f=f.alternate,f!==null&&(f.memoizedState=p),p}function uI(f){var p=cI(f);p.next===null&&(p=f.alternate.memoizedState),vg(f,p.next.queue,{},na())}function OE(){return Ui(jg)}function dI(){return Pr().memoizedState}function fI(){return Pr().memoizedState}function xte(f){for(var p=f.return;p!==null;){switch(p.tag){case 24:case 3:var _=na();f=Vc(_);var S=Gc(p,f,_);S!==null&&(Os(S,p,_),pg(S,p,_)),p={cache:cE()},f.payload=p;return}p=p.return}}function bte(f,p,_){var S=na();_={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},ov(f)?pI(p,_):(_=QM(f,p,_,S),_!==null&&(Os(_,f,S),mI(_,p,S)))}function hI(f,p,_){var S=na();vg(f,p,_,S)}function vg(f,p,_,S){var k={lane:S,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(ov(f))pI(p,k);else{var L=f.alternate;if(f.lanes===0&&(L===null||L.lanes===0)&&(L=p.lastRenderedReducer,L!==null))try{var K=p.lastRenderedState,le=L(K,_);if(k.hasEagerState=!0,k.eagerState=le,Ys(le,K))return Bb(f,p,k,0),Xn===null&&zb(),!1}catch{}finally{}if(_=QM(f,p,k,S),_!==null)return Os(_,f,S),mI(_,p,S),!0}return!1}function LE(f,p,_,S){if(S={lane:2,revertLane:h2(),gesture:null,action:S,hasEagerState:!1,eagerState:null,next:null},ov(f)){if(p)throw Error(r(479))}else p=QM(f,_,S,2),p!==null&&Os(p,f,2)}function ov(f){var p=f.alternate;return f===en||p!==null&&p===en}function pI(f,p){vh=ev=!0;var _=f.pending;_===null?p.next=p:(p.next=_.next,_.next=p),f.pending=p}function mI(f,p,_){if((_&4194048)!==0){var S=p.lanes;S&=f.pendingLanes,_|=S,p.lanes=_,qn(f,_)}}var yg={readContext:Ui,use:rv,useCallback:wr,useContext:wr,useEffect:wr,useImperativeHandle:wr,useLayoutEffect:wr,useInsertionEffect:wr,useMemo:wr,useReducer:wr,useRef:wr,useState:wr,useDebugValue:wr,useDeferredValue:wr,useTransition:wr,useSyncExternalStore:wr,useId:wr,useHostTransitionStatus:wr,useFormState:wr,useActionState:wr,useOptimistic:wr,useMemoCache:wr,useCacheRefresh:wr};yg.useEffectEvent=wr;var gI={readContext:Ui,use:rv,useCallback:function(f,p){return ps().memoizedState=[f,p===void 0?null:p],f},useContext:Ui,useEffect:Jj,useImperativeHandle:function(f,p,_){_=_!=null?_.concat([f]):null,sv(4194308,4,rI.bind(null,p,f),_)},useLayoutEffect:function(f,p){return sv(4194308,4,f,p)},useInsertionEffect:function(f,p){sv(4,2,f,p)},useMemo:function(f,p){var _=ps();p=p===void 0?null:p;var S=f();if(kd){Qe(!0);try{f()}finally{Qe(!1)}}return _.memoizedState=[S,p],S},useReducer:function(f,p,_){var S=ps();if(_!==void 0){var k=_(p);if(kd){Qe(!0);try{_(p)}finally{Qe(!1)}}}else k=p;return S.memoizedState=S.baseState=k,f={pending:null,lanes:0,dispatch:null,lastRenderedReducer:f,lastRenderedState:k},S.queue=f,f=f.dispatch=bte.bind(null,en,f),[S.memoizedState,f]},useRef:function(f){var p=ps();return f={current:f},p.memoizedState=f},useState:function(f){f=CE(f);var p=f.queue,_=hI.bind(null,en,p);return p.dispatch=_,[f.memoizedState,_]},useDebugValue:RE,useDeferredValue:function(f,p){var _=ps();return PE(_,f,p)},useTransition:function(){var f=CE(!1);return f=lI.bind(null,en,f.queue,!0,!1),ps().memoizedState=f,[!1,f]},useSyncExternalStore:function(f,p,_){var S=en,k=ps();if(yn){if(_===void 0)throw Error(r(407));_=_()}else{if(_=p(),Xn===null)throw Error(r(349));(xn&127)!==0||Ij(S,p,_)}k.memoizedState=_;var L={value:_,getSnapshot:p};return k.queue=L,Jj(Fj.bind(null,S,L,f),[f]),S.flags|=2048,_h(9,{destroy:void 0},Uj.bind(null,S,L,_,p),null),_},useId:function(){var f=ps(),p=Xn.identifierPrefix;if(yn){var _=Io,S=jo;_=(S&~(1<<32-Be(S)-1)).toString(32)+_,p="_"+p+"R_"+_,_=tv++,0<_&&(p+="H"+_.toString(32)),p+="_"}else _=dte++,p="_"+p+"r_"+_.toString(32)+"_";return f.memoizedState=p},useHostTransitionStatus:OE,useFormState:Xj,useActionState:Xj,useOptimistic:function(f){var p=ps();p.memoizedState=p.baseState=f;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return p.queue=_,p=LE.bind(null,en,!0,_),_.dispatch=p,[f,p]},useMemoCache:EE,useCacheRefresh:function(){return ps().memoizedState=xte.bind(null,en)},useEffectEvent:function(f){var p=ps(),_={impl:f};return p.memoizedState=_,function(){if((Cn&2)!==0)throw Error(r(440));return _.impl.apply(void 0,arguments)}}},jE={readContext:Ui,use:rv,useCallback:sI,useContext:Ui,useEffect:kE,useImperativeHandle:iI,useInsertionEffect:tI,useLayoutEffect:nI,useMemo:aI,useReducer:iv,useRef:Qj,useState:function(){return iv(Sl)},useDebugValue:RE,useDeferredValue:function(f,p){var _=Pr();return oI(_,In.memoizedState,f,p)},useTransition:function(){var f=iv(Sl)[0],p=Pr().memoizedState;return[typeof f=="boolean"?f:bg(f),p]},useSyncExternalStore:jj,useId:dI,useHostTransitionStatus:OE,useFormState:Kj,useActionState:Kj,useOptimistic:function(f,p){var _=Pr();return Hj(_,In,f,p)},useMemoCache:EE,useCacheRefresh:fI};jE.useEffectEvent=eI;var xI={readContext:Ui,use:rv,useCallback:sI,useContext:Ui,useEffect:kE,useImperativeHandle:iI,useInsertionEffect:tI,useLayoutEffect:nI,useMemo:aI,useReducer:TE,useRef:Qj,useState:function(){return TE(Sl)},useDebugValue:RE,useDeferredValue:function(f,p){var _=Pr();return In===null?PE(_,f,p):oI(_,In.memoizedState,f,p)},useTransition:function(){var f=TE(Sl)[0],p=Pr().memoizedState;return[typeof f=="boolean"?f:bg(f),p]},useSyncExternalStore:jj,useId:dI,useHostTransitionStatus:OE,useFormState:Zj,useActionState:Zj,useOptimistic:function(f,p){var _=Pr();return In!==null?Hj(_,In,f,p):(_.baseState=f,[f,_.queue.dispatch])},useMemoCache:EE,useCacheRefresh:fI};xI.useEffectEvent=eI;function IE(f,p,_,S){p=f.memoizedState,_=_(S,p),_=_==null?p:h({},p,_),f.memoizedState=_,f.lanes===0&&(f.updateQueue.baseState=_)}var UE={enqueueSetState:function(f,p,_){f=f._reactInternals;var S=na(),k=Vc(S);k.payload=p,_!=null&&(k.callback=_),p=Gc(f,k,S),p!==null&&(Os(p,f,S),pg(p,f,S))},enqueueReplaceState:function(f,p,_){f=f._reactInternals;var S=na(),k=Vc(S);k.tag=1,k.payload=p,_!=null&&(k.callback=_),p=Gc(f,k,S),p!==null&&(Os(p,f,S),pg(p,f,S))},enqueueForceUpdate:function(f,p){f=f._reactInternals;var _=na(),S=Vc(_);S.tag=2,p!=null&&(S.callback=p),p=Gc(f,S,_),p!==null&&(Os(p,f,_),pg(p,f,_))}};function bI(f,p,_,S,k,L,K){return f=f.stateNode,typeof f.shouldComponentUpdate=="function"?f.shouldComponentUpdate(S,L,K):p.prototype&&p.prototype.isPureReactComponent?!ag(_,S)||!ag(k,L):!0}function vI(f,p,_,S){f=p.state,typeof p.componentWillReceiveProps=="function"&&p.componentWillReceiveProps(_,S),typeof p.UNSAFE_componentWillReceiveProps=="function"&&p.UNSAFE_componentWillReceiveProps(_,S),p.state!==f&&UE.enqueueReplaceState(p,p.state,null)}function Rd(f,p){var _=p;if("ref"in p){_={};for(var S in p)S!=="ref"&&(_[S]=p[S])}if(f=f.defaultProps){_===p&&(_=h({},_));for(var k in f)_[k]===void 0&&(_[k]=f[k])}return _}function yI(f){Fb(f)}function _I(f){console.error(f)}function SI(f){Fb(f)}function lv(f,p){try{var _=f.onUncaughtError;_(p.value,{componentStack:p.stack})}catch(S){setTimeout(function(){throw S})}}function wI(f,p,_){try{var S=f.onCaughtError;S(_.value,{componentStack:_.stack,errorBoundary:p.tag===1?p.stateNode:null})}catch(k){setTimeout(function(){throw k})}}function FE(f,p,_){return _=Vc(_),_.tag=3,_.payload={element:null},_.callback=function(){lv(f,p)},_}function MI(f){return f=Vc(f),f.tag=3,f}function EI(f,p,_,S){var k=_.type.getDerivedStateFromError;if(typeof k=="function"){var L=S.value;f.payload=function(){return k(L)},f.callback=function(){wI(p,_,S)}}var K=_.stateNode;K!==null&&typeof K.componentDidCatch=="function"&&(f.callback=function(){wI(p,_,S),typeof k!="function"&&(Zc===null?Zc=new Set([this]):Zc.add(this));var le=S.stack;this.componentDidCatch(S.value,{componentStack:le!==null?le:""})})}function vte(f,p,_,S,k){if(_.flags|=32768,S!==null&&typeof S=="object"&&typeof S.then=="function"){if(p=_.alternate,p!==null&&hh(p,_,k,!0),_=Qs.current,_!==null){switch(_.tag){case 31:case 13:return Ea===null?yv():_.alternate===null&&Mr===0&&(Mr=3),_.flags&=-257,_.flags|=65536,_.lanes=k,S===Kb?_.flags|=16384:(p=_.updateQueue,p===null?_.updateQueue=new Set([S]):p.add(S),u2(f,S,k)),!1;case 22:return _.flags|=65536,S===Kb?_.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)),u2(f,S,k)),!1}throw Error(r(435,_.tag))}return u2(f,S,k),yv(),!1}if(yn)return p=Qs.current,p!==null?((p.flags&65536)===0&&(p.flags|=256),p.flags|=65536,p.lanes=k,S!==iE&&(f=Error(r(422),{cause:S}),cg(_a(f,_)))):(S!==iE&&(p=Error(r(423),{cause:S}),cg(_a(p,_))),f=f.current.alternate,f.flags|=65536,k&=-k,f.lanes|=k,S=_a(S,_),k=FE(f.stateNode,S,k),mE(f,k),Mr!==4&&(Mr=2)),!1;var L=Error(r(520),{cause:S});if(L=_a(L,_),Cg===null?Cg=[L]:Cg.push(L),Mr!==4&&(Mr=2),p===null)return!0;S=_a(S,_),_=p;do{switch(_.tag){case 3:return _.flags|=65536,f=k&-k,_.lanes|=f,f=FE(_.stateNode,S,f),mE(_,f),!1;case 1:if(p=_.type,L=_.stateNode,(_.flags&128)===0&&(typeof p.getDerivedStateFromError=="function"||L!==null&&typeof L.componentDidCatch=="function"&&(Zc===null||!Zc.has(L))))return _.flags|=65536,k&=-k,_.lanes|=k,k=MI(k),EI(k,f,_,S),mE(_,k),!1}_=_.return}while(_!==null);return!1}var zE=Error(r(461)),zr=!1;function Fi(f,p,_,S){p.child=f===null?Nj(p,null,_,S):Nd(p,f.child,_,S)}function AI(f,p,_,S,k){_=_.render;var L=p.ref;if("ref"in S){var K={};for(var le in S)le!=="ref"&&(K[le]=S[le])}else K=S;return Ed(p),S=_E(f,p,_,K,L,k),le=SE(),f!==null&&!zr?(wE(f,p,k),wl(f,p,k)):(yn&&le&&nE(p),p.flags|=1,Fi(f,p,S,k),p.child)}function TI(f,p,_,S,k){if(f===null){var L=_.type;return typeof L=="function"&&!JM(L)&&L.defaultProps===void 0&&_.compare===null?(p.tag=15,p.type=L,CI(f,p,L,S,k)):(f=$b(_.type,null,S,p,p.mode,k),f.ref=p.ref,f.return=p,p.child=f)}if(L=f.child,!XE(f,k)){var K=L.memoizedProps;if(_=_.compare,_=_!==null?_:ag,_(K,S)&&f.ref===p.ref)return wl(f,p,k)}return p.flags|=1,f=xl(L,S),f.ref=p.ref,f.return=p,p.child=f}function CI(f,p,_,S,k){if(f!==null){var L=f.memoizedProps;if(ag(L,S)&&f.ref===p.ref)if(zr=!1,p.pendingProps=S=L,XE(f,k))(f.flags&131072)!==0&&(zr=!0);else return p.lanes=f.lanes,wl(f,p,k)}return BE(f,p,_,S,k)}function NI(f,p,_,S){var k=S.children,L=f!==null?f.memoizedState:null;if(f===null&&p.stateNode===null&&(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),S.mode==="hidden"){if((p.flags&128)!==0){if(L=L!==null?L.baseLanes|_:_,f!==null){for(S=p.child=f.child,k=0;S!==null;)k=k|S.lanes|S.childLanes,S=S.sibling;S=k&~L}else S=0,p.child=null;return kI(f,p,L,_,S)}if((_&536870912)!==0)p.memoizedState={baseLanes:0,cachePool:null},f!==null&&qb(p,L!==null?L.cachePool:null),L!==null?Pj(p,L):xE(),Dj(p);else return S=p.lanes=536870912,kI(f,p,L!==null?L.baseLanes|_:_,_,S)}else L!==null?(qb(p,L.cachePool),Pj(p,L),qc(),p.memoizedState=null):(f!==null&&qb(p,null),xE(),qc());return Fi(f,p,k,_),p.child}function _g(f,p){return f!==null&&f.tag===22||p.stateNode!==null||(p.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),p.sibling}function kI(f,p,_,S,k){var L=dE();return L=L===null?null:{parent:Ur._currentValue,pool:L},p.memoizedState={baseLanes:_,cachePool:L},f!==null&&qb(p,null),xE(),Dj(p),f!==null&&hh(f,p,S,!0),p.childLanes=k,null}function cv(f,p){return p=dv({mode:p.mode,children:p.children},f.mode),p.ref=f.ref,f.child=p,p.return=f,p}function RI(f,p,_){return Nd(p,f.child,null,_),f=cv(p,p.pendingProps),f.flags|=2,Js(p),p.memoizedState=null,f}function yte(f,p,_){var S=p.pendingProps,k=(p.flags&128)!==0;if(p.flags&=-129,f===null){if(yn){if(S.mode==="hidden")return f=cv(p,S),p.lanes=536870912,_g(null,f);if(vE(p),(f=nr)?(f=$4(f,Ma),f=f!==null&&f.data==="&"?f:null,f!==null&&(p.memoizedState={dehydrated:f,treeContext:Fc!==null?{id:jo,overflow:Io}:null,retryLane:536870912,hydrationErrors:null},_=pj(f),_.return=p,p.child=_,Ii=p,nr=null)):f=null,f===null)throw Bc(p);return p.lanes=536870912,null}return cv(p,S)}var L=f.memoizedState;if(L!==null){var K=L.dehydrated;if(vE(p),k)if(p.flags&256)p.flags&=-257,p=RI(f,p,_);else if(p.memoizedState!==null)p.child=f.child,p.flags|=128,p=null;else throw Error(r(558));else if(zr||hh(f,p,_,!1),k=(_&f.childLanes)!==0,zr||k){if(S=Xn,S!==null&&(K=bi(S,_),K!==0&&K!==L.retryLane))throw L.retryLane=K,_d(f,K),Os(S,f,K),zE;yv(),p=RI(f,p,_)}else f=L.treeContext,nr=Aa(K.nextSibling),Ii=p,yn=!0,zc=null,Ma=!1,f!==null&&xj(p,f),p=cv(p,S),p.flags|=4096;return p}return f=xl(f.child,{mode:S.mode,children:S.children}),f.ref=p.ref,p.child=f,f.return=p,f}function uv(f,p){var _=p.ref;if(_===null)f!==null&&f.ref!==null&&(p.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(r(284));(f===null||f.ref!==_)&&(p.flags|=4194816)}}function BE(f,p,_,S,k){return Ed(p),_=_E(f,p,_,S,void 0,k),S=SE(),f!==null&&!zr?(wE(f,p,k),wl(f,p,k)):(yn&&S&&nE(p),p.flags|=1,Fi(f,p,_,k),p.child)}function PI(f,p,_,S,k,L){return Ed(p),p.updateQueue=null,_=Lj(p,S,_,k),Oj(f),S=SE(),f!==null&&!zr?(wE(f,p,L),wl(f,p,L)):(yn&&S&&nE(p),p.flags|=1,Fi(f,p,_,L),p.child)}function DI(f,p,_,S,k){if(Ed(p),p.stateNode===null){var L=ch,K=_.contextType;typeof K=="object"&&K!==null&&(L=Ui(K)),L=new _(S,L),p.memoizedState=L.state!==null&&L.state!==void 0?L.state:null,L.updater=UE,p.stateNode=L,L._reactInternals=p,L=p.stateNode,L.props=S,L.state=p.memoizedState,L.refs={},hE(p),K=_.contextType,L.context=typeof K=="object"&&K!==null?Ui(K):ch,L.state=p.memoizedState,K=_.getDerivedStateFromProps,typeof K=="function"&&(IE(p,_,K,S),L.state=p.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof L.getSnapshotBeforeUpdate=="function"||typeof L.UNSAFE_componentWillMount!="function"&&typeof L.componentWillMount!="function"||(K=L.state,typeof L.componentWillMount=="function"&&L.componentWillMount(),typeof L.UNSAFE_componentWillMount=="function"&&L.UNSAFE_componentWillMount(),K!==L.state&&UE.enqueueReplaceState(L,L.state,null),gg(p,S,L,k),mg(),L.state=p.memoizedState),typeof L.componentDidMount=="function"&&(p.flags|=4194308),S=!0}else if(f===null){L=p.stateNode;var le=p.memoizedProps,we=Rd(_,le);L.props=we;var $e=L.context,st=_.contextType;K=ch,typeof st=="object"&&st!==null&&(K=Ui(st));var dt=_.getDerivedStateFromProps;st=typeof dt=="function"||typeof L.getSnapshotBeforeUpdate=="function",le=p.pendingProps!==le,st||typeof L.UNSAFE_componentWillReceiveProps!="function"&&typeof L.componentWillReceiveProps!="function"||(le||$e!==K)&&vI(p,L,S,K),$c=!1;var We=p.memoizedState;L.state=We,gg(p,S,L,k),mg(),$e=p.memoizedState,le||We!==$e||$c?(typeof dt=="function"&&(IE(p,_,dt,S),$e=p.memoizedState),(we=$c||bI(p,_,we,S,We,$e,K))?(st||typeof L.UNSAFE_componentWillMount!="function"&&typeof L.componentWillMount!="function"||(typeof L.componentWillMount=="function"&&L.componentWillMount(),typeof L.UNSAFE_componentWillMount=="function"&&L.UNSAFE_componentWillMount()),typeof L.componentDidMount=="function"&&(p.flags|=4194308)):(typeof L.componentDidMount=="function"&&(p.flags|=4194308),p.memoizedProps=S,p.memoizedState=$e),L.props=S,L.state=$e,L.context=K,S=we):(typeof L.componentDidMount=="function"&&(p.flags|=4194308),S=!1)}else{L=p.stateNode,pE(f,p),K=p.memoizedProps,st=Rd(_,K),L.props=st,dt=p.pendingProps,We=L.context,$e=_.contextType,we=ch,typeof $e=="object"&&$e!==null&&(we=Ui($e)),le=_.getDerivedStateFromProps,($e=typeof le=="function"||typeof L.getSnapshotBeforeUpdate=="function")||typeof L.UNSAFE_componentWillReceiveProps!="function"&&typeof L.componentWillReceiveProps!="function"||(K!==dt||We!==we)&&vI(p,L,S,we),$c=!1,We=p.memoizedState,L.state=We,gg(p,S,L,k),mg();var nt=p.memoizedState;K!==dt||We!==nt||$c||f!==null&&f.dependencies!==null&&Gb(f.dependencies)?(typeof le=="function"&&(IE(p,_,le,S),nt=p.memoizedState),(st=$c||bI(p,_,st,S,We,nt,we)||f!==null&&f.dependencies!==null&&Gb(f.dependencies))?($e||typeof L.UNSAFE_componentWillUpdate!="function"&&typeof L.componentWillUpdate!="function"||(typeof L.componentWillUpdate=="function"&&L.componentWillUpdate(S,nt,we),typeof L.UNSAFE_componentWillUpdate=="function"&&L.UNSAFE_componentWillUpdate(S,nt,we)),typeof L.componentDidUpdate=="function"&&(p.flags|=4),typeof L.getSnapshotBeforeUpdate=="function"&&(p.flags|=1024)):(typeof L.componentDidUpdate!="function"||K===f.memoizedProps&&We===f.memoizedState||(p.flags|=4),typeof L.getSnapshotBeforeUpdate!="function"||K===f.memoizedProps&&We===f.memoizedState||(p.flags|=1024),p.memoizedProps=S,p.memoizedState=nt),L.props=S,L.state=nt,L.context=we,S=st):(typeof L.componentDidUpdate!="function"||K===f.memoizedProps&&We===f.memoizedState||(p.flags|=4),typeof L.getSnapshotBeforeUpdate!="function"||K===f.memoizedProps&&We===f.memoizedState||(p.flags|=1024),S=!1)}return L=S,uv(f,p),S=(p.flags&128)!==0,L||S?(L=p.stateNode,_=S&&typeof _.getDerivedStateFromError!="function"?null:L.render(),p.flags|=1,f!==null&&S?(p.child=Nd(p,f.child,null,k),p.child=Nd(p,null,_,k)):Fi(f,p,_,k),p.memoizedState=L.state,f=p.child):f=wl(f,p,k),f}function OI(f,p,_,S){return wd(),p.flags|=256,Fi(f,p,_,S),p.child}var HE={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function $E(f){return{baseLanes:f,cachePool:wj()}}function VE(f,p,_){return f=f!==null?f.childLanes&~_:0,p&&(f|=ta),f}function LI(f,p,_){var S=p.pendingProps,k=!1,L=(p.flags&128)!==0,K;if((K=L)||(K=f!==null&&f.memoizedState===null?!1:(Rr.current&2)!==0),K&&(k=!0,p.flags&=-129),K=(p.flags&32)!==0,p.flags&=-33,f===null){if(yn){if(k?Wc(p):qc(),(f=nr)?(f=$4(f,Ma),f=f!==null&&f.data!=="&"?f:null,f!==null&&(p.memoizedState={dehydrated:f,treeContext:Fc!==null?{id:jo,overflow:Io}:null,retryLane:536870912,hydrationErrors:null},_=pj(f),_.return=p,p.child=_,Ii=p,nr=null)):f=null,f===null)throw Bc(p);return A2(f)?p.lanes=32:p.lanes=536870912,null}var le=S.children;return S=S.fallback,k?(qc(),k=p.mode,le=dv({mode:"hidden",children:le},k),S=Sd(S,k,_,null),le.return=p,S.return=p,le.sibling=S,p.child=le,S=p.child,S.memoizedState=$E(_),S.childLanes=VE(f,K,_),p.memoizedState=HE,_g(null,S)):(Wc(p),GE(p,le))}var we=f.memoizedState;if(we!==null&&(le=we.dehydrated,le!==null)){if(L)p.flags&256?(Wc(p),p.flags&=-257,p=WE(f,p,_)):p.memoizedState!==null?(qc(),p.child=f.child,p.flags|=128,p=null):(qc(),le=S.fallback,k=p.mode,S=dv({mode:"visible",children:S.children},k),le=Sd(le,k,_,null),le.flags|=2,S.return=p,le.return=p,S.sibling=le,p.child=S,Nd(p,f.child,null,_),S=p.child,S.memoizedState=$E(_),S.childLanes=VE(f,K,_),p.memoizedState=HE,p=_g(null,S));else if(Wc(p),A2(le)){if(K=le.nextSibling&&le.nextSibling.dataset,K)var $e=K.dgst;K=$e,S=Error(r(419)),S.stack="",S.digest=K,cg({value:S,source:null,stack:null}),p=WE(f,p,_)}else if(zr||hh(f,p,_,!1),K=(_&f.childLanes)!==0,zr||K){if(K=Xn,K!==null&&(S=bi(K,_),S!==0&&S!==we.retryLane))throw we.retryLane=S,_d(f,S),Os(K,f,S),zE;E2(le)||yv(),p=WE(f,p,_)}else E2(le)?(p.flags|=192,p.child=f.child,p=null):(f=we.treeContext,nr=Aa(le.nextSibling),Ii=p,yn=!0,zc=null,Ma=!1,f!==null&&xj(p,f),p=GE(p,S.children),p.flags|=4096);return p}return k?(qc(),le=S.fallback,k=p.mode,we=f.child,$e=we.sibling,S=xl(we,{mode:"hidden",children:S.children}),S.subtreeFlags=we.subtreeFlags&65011712,$e!==null?le=xl($e,le):(le=Sd(le,k,_,null),le.flags|=2),le.return=p,S.return=p,S.sibling=le,p.child=S,_g(null,S),S=p.child,le=f.child.memoizedState,le===null?le=$E(_):(k=le.cachePool,k!==null?(we=Ur._currentValue,k=k.parent!==we?{parent:we,pool:we}:k):k=wj(),le={baseLanes:le.baseLanes|_,cachePool:k}),S.memoizedState=le,S.childLanes=VE(f,K,_),p.memoizedState=HE,_g(f.child,S)):(Wc(p),_=f.child,f=_.sibling,_=xl(_,{mode:"visible",children:S.children}),_.return=p,_.sibling=null,f!==null&&(K=p.deletions,K===null?(p.deletions=[f],p.flags|=16):K.push(f)),p.child=_,p.memoizedState=null,_)}function GE(f,p){return p=dv({mode:"visible",children:p},f.mode),p.return=f,f.child=p}function dv(f,p){return f=Zs(22,f,null,p),f.lanes=0,f}function WE(f,p,_){return Nd(p,f.child,null,_),f=GE(p,p.pendingProps.children),f.flags|=2,p.memoizedState=null,f}function jI(f,p,_){f.lanes|=p;var S=f.alternate;S!==null&&(S.lanes|=p),oE(f.return,p,_)}function qE(f,p,_,S,k,L){var K=f.memoizedState;K===null?f.memoizedState={isBackwards:p,rendering:null,renderingStartTime:0,last:S,tail:_,tailMode:k,treeForkCount:L}:(K.isBackwards=p,K.rendering=null,K.renderingStartTime=0,K.last=S,K.tail=_,K.tailMode=k,K.treeForkCount=L)}function II(f,p,_){var S=p.pendingProps,k=S.revealOrder,L=S.tail;S=S.children;var K=Rr.current,le=(K&2)!==0;if(le?(K=K&1|2,p.flags|=128):K&=1,ee(Rr,K),Fi(f,p,S,_),S=yn?lg:0,!le&&f!==null&&(f.flags&128)!==0)e:for(f=p.child;f!==null;){if(f.tag===13)f.memoizedState!==null&&jI(f,_,p);else if(f.tag===19)jI(f,_,p);else if(f.child!==null){f.child.return=f,f=f.child;continue}if(f===p)break e;for(;f.sibling===null;){if(f.return===null||f.return===p)break e;f=f.return}f.sibling.return=f.return,f=f.sibling}switch(k){case"forwards":for(_=p.child,k=null;_!==null;)f=_.alternate,f!==null&&Jb(f)===null&&(k=_),_=_.sibling;_=k,_===null?(k=p.child,p.child=null):(k=_.sibling,_.sibling=null),qE(p,!1,k,_,L,S);break;case"backwards":case"unstable_legacy-backwards":for(_=null,k=p.child,p.child=null;k!==null;){if(f=k.alternate,f!==null&&Jb(f)===null){p.child=k;break}f=k.sibling,k.sibling=_,_=k,k=f}qE(p,!0,_,null,L,S);break;case"together":qE(p,!1,null,null,void 0,S);break;default:p.memoizedState=null}return p.child}function wl(f,p,_){if(f!==null&&(p.dependencies=f.dependencies),Yc|=p.lanes,(_&p.childLanes)===0)if(f!==null){if(hh(f,p,_,!1),(_&p.childLanes)===0)return null}else return null;if(f!==null&&p.child!==f.child)throw Error(r(153));if(p.child!==null){for(f=p.child,_=xl(f,f.pendingProps),p.child=_,_.return=p;f.sibling!==null;)f=f.sibling,_=_.sibling=xl(f,f.pendingProps),_.return=p;_.sibling=null}return p.child}function XE(f,p){return(f.lanes&p)!==0?!0:(f=f.dependencies,!!(f!==null&&Gb(f)))}function _te(f,p,_){switch(p.tag){case 3:Ce(p,p.stateNode.containerInfo),Hc(p,Ur,f.memoizedState.cache),wd();break;case 27:case 5:_e(p);break;case 4:Ce(p,p.stateNode.containerInfo);break;case 10:Hc(p,p.type,p.memoizedProps.value);break;case 31:if(p.memoizedState!==null)return p.flags|=128,vE(p),null;break;case 13:var S=p.memoizedState;if(S!==null)return S.dehydrated!==null?(Wc(p),p.flags|=128,null):(_&p.child.childLanes)!==0?LI(f,p,_):(Wc(p),f=wl(f,p,_),f!==null?f.sibling:null);Wc(p);break;case 19:var k=(f.flags&128)!==0;if(S=(_&p.childLanes)!==0,S||(hh(f,p,_,!1),S=(_&p.childLanes)!==0),k){if(S)return II(f,p,_);p.flags|=128}if(k=p.memoizedState,k!==null&&(k.rendering=null,k.tail=null,k.lastEffect=null),ee(Rr,Rr.current),S)break;return null;case 22:return p.lanes=0,NI(f,p,_,p.pendingProps);case 24:Hc(p,Ur,f.memoizedState.cache)}return wl(f,p,_)}function UI(f,p,_){if(f!==null)if(f.memoizedProps!==p.pendingProps)zr=!0;else{if(!XE(f,_)&&(p.flags&128)===0)return zr=!1,_te(f,p,_);zr=(f.flags&131072)!==0}else zr=!1,yn&&(p.flags&1048576)!==0&&gj(p,lg,p.index);switch(p.lanes=0,p.tag){case 16:e:{var S=p.pendingProps;if(f=Td(p.elementType),p.type=f,typeof f=="function")JM(f)?(S=Rd(f,S),p.tag=1,p=DI(null,p,f,S,_)):(p.tag=0,p=BE(null,p,f,S,_));else{if(f!=null){var k=f.$$typeof;if(k===A){p.tag=11,p=AI(null,p,f,S,_);break e}else if(k===C){p.tag=14,p=TI(null,p,f,S,_);break e}}throw p=D(f)||f,Error(r(306,p,""))}}return p;case 0:return BE(f,p,p.type,p.pendingProps,_);case 1:return S=p.type,k=Rd(S,p.pendingProps),DI(f,p,S,k,_);case 3:e:{if(Ce(p,p.stateNode.containerInfo),f===null)throw Error(r(387));S=p.pendingProps;var L=p.memoizedState;k=L.element,pE(f,p),gg(p,S,null,_);var K=p.memoizedState;if(S=K.cache,Hc(p,Ur,S),S!==L.cache&&lE(p,[Ur],_,!0),mg(),S=K.element,L.isDehydrated)if(L={element:S,isDehydrated:!1,cache:K.cache},p.updateQueue.baseState=L,p.memoizedState=L,p.flags&256){p=OI(f,p,S,_);break e}else if(S!==k){k=_a(Error(r(424)),p),cg(k),p=OI(f,p,S,_);break e}else{switch(f=p.stateNode.containerInfo,f.nodeType){case 9:f=f.body;break;default:f=f.nodeName==="HTML"?f.ownerDocument.body:f}for(nr=Aa(f.firstChild),Ii=p,yn=!0,zc=null,Ma=!0,_=Nj(p,null,S,_),p.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling}else{if(wd(),S===k){p=wl(f,p,_);break e}Fi(f,p,S,_)}p=p.child}return p;case 26:return uv(f,p),f===null?(_=K4(p.type,null,p.pendingProps,null))?p.memoizedState=_:yn||(_=p.type,f=p.pendingProps,S=Tv(ie.current).createElement(_),S[mr]=p,S[Nr]=f,zi(S,_,f),Re(S),p.stateNode=S):p.memoizedState=K4(p.type,f.memoizedProps,p.pendingProps,f.memoizedState),null;case 27:return _e(p),f===null&&yn&&(S=p.stateNode=W4(p.type,p.pendingProps,ie.current),Ii=p,Ma=!0,k=nr,tu(p.type)?(T2=k,nr=Aa(S.firstChild)):nr=k),Fi(f,p,p.pendingProps.children,_),uv(f,p),f===null&&(p.flags|=4194304),p.child;case 5:return f===null&&yn&&((k=S=nr)&&(S=Zte(S,p.type,p.pendingProps,Ma),S!==null?(p.stateNode=S,Ii=p,nr=Aa(S.firstChild),Ma=!1,k=!0):k=!1),k||Bc(p)),_e(p),k=p.type,L=p.pendingProps,K=f!==null?f.memoizedProps:null,S=L.children,S2(k,L)?S=null:K!==null&&S2(k,K)&&(p.flags|=32),p.memoizedState!==null&&(k=_E(f,p,fte,null,null,_),jg._currentValue=k),uv(f,p),Fi(f,p,S,_),p.child;case 6:return f===null&&yn&&((f=_=nr)&&(_=Qte(_,p.pendingProps,Ma),_!==null?(p.stateNode=_,Ii=p,nr=null,f=!0):f=!1),f||Bc(p)),null;case 13:return LI(f,p,_);case 4:return Ce(p,p.stateNode.containerInfo),S=p.pendingProps,f===null?p.child=Nd(p,null,S,_):Fi(f,p,S,_),p.child;case 11:return AI(f,p,p.type,p.pendingProps,_);case 7:return Fi(f,p,p.pendingProps,_),p.child;case 8:return Fi(f,p,p.pendingProps.children,_),p.child;case 12:return Fi(f,p,p.pendingProps.children,_),p.child;case 10:return S=p.pendingProps,Hc(p,p.type,S.value),Fi(f,p,S.children,_),p.child;case 9:return k=p.type._context,S=p.pendingProps.children,Ed(p),k=Ui(k),S=S(k),p.flags|=1,Fi(f,p,S,_),p.child;case 14:return TI(f,p,p.type,p.pendingProps,_);case 15:return CI(f,p,p.type,p.pendingProps,_);case 19:return II(f,p,_);case 31:return yte(f,p,_);case 22:return NI(f,p,_,p.pendingProps);case 24:return Ed(p),S=Ui(Ur),f===null?(k=dE(),k===null&&(k=Xn,L=cE(),k.pooledCache=L,L.refCount++,L!==null&&(k.pooledCacheLanes|=_),k=L),p.memoizedState={parent:S,cache:k},hE(p),Hc(p,Ur,k)):((f.lanes&_)!==0&&(pE(f,p),gg(p,null,null,_),mg()),k=f.memoizedState,L=p.memoizedState,k.parent!==S?(k={parent:S,cache:S},p.memoizedState=k,p.lanes===0&&(p.memoizedState=p.updateQueue.baseState=k),Hc(p,Ur,S)):(S=L.cache,Hc(p,Ur,S),S!==k.cache&&lE(p,[Ur],_,!0))),Fi(f,p,p.pendingProps.children,_),p.child;case 29:throw p.pendingProps}throw Error(r(156,p.tag))}function Ml(f){f.flags|=4}function KE(f,p,_,S,k){if((p=(f.mode&32)!==0)&&(p=!1),p){if(f.flags|=16777216,(k&335544128)===k)if(f.stateNode.complete)f.flags|=8192;else if(d4())f.flags|=8192;else throw Cd=Kb,fE}else f.flags&=-16777217}function FI(f,p){if(p.type!=="stylesheet"||(p.state.loading&4)!==0)f.flags&=-16777217;else if(f.flags|=16777216,!eU(p))if(d4())f.flags|=8192;else throw Cd=Kb,fE}function fv(f,p){p!==null&&(f.flags|=4),f.flags&16384&&(p=f.tag!==22?yt():536870912,f.lanes|=p,Eh|=p)}function Sg(f,p){if(!yn)switch(f.tailMode){case"hidden":p=f.tail;for(var _=null;p!==null;)p.alternate!==null&&(_=p),p=p.sibling;_===null?f.tail=null:_.sibling=null;break;case"collapsed":_=f.tail;for(var S=null;_!==null;)_.alternate!==null&&(S=_),_=_.sibling;S===null?p||f.tail===null?f.tail=null:f.tail.sibling=null:S.sibling=null}}function rr(f){var p=f.alternate!==null&&f.alternate.child===f.child,_=0,S=0;if(p)for(var k=f.child;k!==null;)_|=k.lanes|k.childLanes,S|=k.subtreeFlags&65011712,S|=k.flags&65011712,k.return=f,k=k.sibling;else for(k=f.child;k!==null;)_|=k.lanes|k.childLanes,S|=k.subtreeFlags,S|=k.flags,k.return=f,k=k.sibling;return f.subtreeFlags|=S,f.childLanes=_,p}function Ste(f,p,_){var S=p.pendingProps;switch(rE(p),p.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return rr(p),null;case 1:return rr(p),null;case 3:return _=p.stateNode,S=null,f!==null&&(S=f.memoizedState.cache),p.memoizedState.cache!==S&&(p.flags|=2048),yl(Ur),fe(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(f===null||f.child===null)&&(fh(p)?Ml(p):f===null||f.memoizedState.isDehydrated&&(p.flags&256)===0||(p.flags|=1024,sE())),rr(p),null;case 26:var k=p.type,L=p.memoizedState;return f===null?(Ml(p),L!==null?(rr(p),FI(p,L)):(rr(p),KE(p,k,null,S,_))):L?L!==f.memoizedState?(Ml(p),rr(p),FI(p,L)):(rr(p),p.flags&=-16777217):(f=f.memoizedProps,f!==S&&Ml(p),rr(p),KE(p,k,f,S,_)),null;case 27:if(Oe(p),_=ie.current,k=p.type,f!==null&&p.stateNode!=null)f.memoizedProps!==S&&Ml(p);else{if(!S){if(p.stateNode===null)throw Error(r(166));return rr(p),null}f=ae.current,fh(p)?bj(p):(f=W4(k,S,_),p.stateNode=f,Ml(p))}return rr(p),null;case 5:if(Oe(p),k=p.type,f!==null&&p.stateNode!=null)f.memoizedProps!==S&&Ml(p);else{if(!S){if(p.stateNode===null)throw Error(r(166));return rr(p),null}if(L=ae.current,fh(p))bj(p);else{var K=Tv(ie.current);switch(L){case 1:L=K.createElementNS("http://www.w3.org/2000/svg",k);break;case 2:L=K.createElementNS("http://www.w3.org/1998/Math/MathML",k);break;default:switch(k){case"svg":L=K.createElementNS("http://www.w3.org/2000/svg",k);break;case"math":L=K.createElementNS("http://www.w3.org/1998/Math/MathML",k);break;case"script":L=K.createElement("div"),L.innerHTML=" - + +