diff --git a/dist/hornverifier b/dist/hornverifier index 52827032..051175f3 100755 Binary files a/dist/hornverifier and b/dist/hornverifier differ diff --git a/dist/roverd b/dist/roverd index 636b9ada..01f4378c 100755 Binary files a/dist/roverd and b/dist/roverd differ diff --git a/dist/roverd-debian-laptop b/dist/roverd-debian-laptop index 59cb7ce7..0976fb70 100755 Binary files a/dist/roverd-debian-laptop and b/dist/roverd-debian-laptop differ diff --git a/dist/servoverifier b/dist/servoverifier index 9f495f1f..7305b930 100755 Binary files a/dist/servoverifier and b/dist/servoverifier differ diff --git a/docs/rover-peripherals.md b/docs/rover-peripherals.md index 900d62fc..de93291e 100644 --- a/docs/rover-peripherals.md +++ b/docs/rover-peripherals.md @@ -1,6 +1,6 @@ # Boot-discovered rover peripherals -This document defines the planned system for attaching self-describing ESP32 peripherals to a rover over USB. A peripheral advertises a small ordered set of controls, the web UI renders those controls automatically, and the current driver can use them without adding device-specific configuration to the rover or server. +This document defines the system for attaching self-describing ESP32 peripherals to a rover over USB. A peripheral advertises a small ordered set of controls, the web UI renders those controls automatically, and the current driver can use them without adding device-specific configuration to the rover or server. The design deliberately stays small: @@ -11,10 +11,12 @@ The design deliberately stays small: - Controls appear in one vertical column in the order registered by the ESP32 program. - Anyone who can currently drive the rover can use its peripheral controls. - Peripherals are discovered once when `roverd` starts; changing one requires restarting the rover. +- ESP32 firmware is built and uploaded with PlatformIO. +- The same firmware supports CH340/CP210x USB-to-UART boards and native USB CDC boards. - There is no peripheral configuration in the rover configuration file. - There is no separate rover-peripheral protocol version. -This is a design document. The names of proposed Go, JavaScript, and Arduino APIs describe the intended implementation and do not refer to code that already exists. +This document is both the design contract and implementation guide. The PlatformIO firmware library, reference sketch, focused Go Firmata client, hardware probe, boot-time daemon discovery, fixed inventory, generic output dispatch, built-in hardware backend selection, rover WebSocket message shapes, server roster forwarding, and shared HUD renderer now exist. ## System boundary @@ -75,9 +77,12 @@ Custom functions can do anything the ESP32 program can do, including: - Send text to a display. - Operate hardware through an ESP32-specific library. - Change several outputs as one operation. -- Update state used by non-blocking work in `loop()`. +- Update state used by non-blocking work in `updateRoverPeripheral()`. -Application authors use string IDs such as `specialAction` or `animationSpeed`. They do not assign numeric action IDs. Firmata necessarily uses a numeric SysEx feature byte internally, but that is an implementation detail hidden by the peripheral library. +The visible control name is also its string wire identifier, so authors provide +one meaningful name instead of maintaining a second hidden ID. They do not +assign numeric action IDs. Firmata necessarily uses a numeric SysEx feature byte +internally, but that is an implementation detail hidden by the package. ## Firmata user feature @@ -124,11 +129,13 @@ The receiver combines each pair: source byte = encoded byte 1 | (encoded byte 2 << 7) ``` -Peripheral authors never perform this encoding themselves. It belongs in the ESP32 `RoverPeripheralFirmata` library and the Go Firmata client used by `roverd`. +Peripheral authors never perform this encoding themselves. It belongs inside the ESP32 `RoverPeripheral` package and the Go Firmata client used by `roverd`. + +ConfigurableFirmata's ESP32 parser accepts 252 bytes inside one incoming SysEx frame, including the feature and operation bytes. `CONTROL` values are not chunked in this deliberately simple design. The Go client checks the fully encoded message before writing it and returns an error if a particular control value cannot fit, rather than sending a frame the ESP32 would discard. Normal numeric, boolean, and short text controls fit comfortably; a text control's configured length should reflect this transport constraint. ## Peripheral description -The ESP32 library builds this description from the controls registered during `setup()`. The order of the `controls` array is the registration order and is also the UI order. +The ESP32 library builds this description from the controls registered in `configureRoverPeripheral()`. The order of the `controls` array is the registration order and is also the UI order. An example description is: @@ -137,7 +144,7 @@ An example description is: "name": "Example peripheral", "controls": [ { - "id": "servoPosition", + "id": "Servo position", "type": "slider", "name": "Servo position", "min": 0, @@ -148,20 +155,20 @@ An example description is: } }, { - "id": "lightBrightness", + "id": "Light brightness", "type": "slider", "name": "Light brightness", "min": 0, "max": 255, "output": { "type": "pwm", - "pin": 18 + "pin": 17 } }, { - "id": "specialAction", + "id": "Special action", "type": "button", - "name": "Run special action", + "name": "Special action", "mode": "momentary", "output": { "type": "custom" @@ -324,44 +331,35 @@ An ESP32 that supplies all three roles describes: ```json { - "name": "Laptop rover GPIO", + "name": "Rover GPIO", "roverControls": { "cameraServo": { - "output": { - "type": "servo", - "pin": 14 - }, - "minAngle": -15, - "maxAngle": 30, - "homeAngle": 0, + "pin": 14, + "minimumAngleDegrees": -15, + "maximumAngleDegrees": 30, + "homeAngleDegrees": 0, "nudgeDegrees": 2, - "minPulseUs": 900, - "maxPulseUs": 2100, + "minimumPulseMicroseconds": 900, + "maximumPulseMicroseconds": 2100, "allowRawPulse": false, - "invert": false + "inverted": false }, "headlight": { - "output": { - "type": "digital", - "pin": 18 - }, - "initialOn": false, - "activeLow": false + "pin": 18, + "activeLow": false, + "initiallyOn": false }, "laser": { - "output": { - "type": "digital", - "pin": 19 - }, - "initialOn": false, - "activeLow": false + "pin": 16, + "activeLow": false, + "initiallyOn": false } }, "controls": [] } ``` -The ESP32 description owns the calibration for hardware attached to that ESP32. Laptop rover YAML does not repeat the ESP32 pin numbers or servo calibration. +The ESP32 description owns the calibration for hardware attached to that ESP32. Rover YAML does not repeat the ESP32 pin numbers or servo calibration. ### Optional backend-selection rule @@ -373,7 +371,7 @@ The ESP32 description owns the calibration for hardware attached to that ESP32. Native configuration deliberately wins. A Pi rover can attach an ESP32 for unrelated generic controls without unexpectedly moving its existing camera servo, headlight, or laser to the ESP32. To deliberately use the ESP32 for one of those features, disable only that native feature in rover YAML. -The normal laptop configuration keeps the unavailable native GPIO features disabled: +Any rover configuration that should use the ESP32 for these roles keeps the corresponding native GPIO features disabled: ```yaml cameraServo: @@ -386,7 +384,7 @@ laser: enabled: false ``` -An attached ESP32 can then fill any or all of those roles automatically at the next `roverd` start. No USB path or backend name is added to YAML. +An attached ESP32 can then fill any or all of those roles automatically at the next `roverd` start. This works identically on Raspberry Pi and laptop rover hosts; no USB path or backend name is added to YAML. Conflict behavior is fixed and simple: @@ -493,221 +491,44 @@ The Firmata toggle backend converts the logical value using `activeLow` before s ## ESP32 authoring API -Peripheral authors should not write JSON, construct SysEx messages, or manually dispatch control IDs. The proposed `RoverPeripheralFirmata` Arduino library owns those tasks. +Peripheral programs include `RoverPeripheral.h` and define +`configureRoverPeripheral()`. The library provides serial setup, Firmata setup, +`setup()`, and `loop()`. -A long positional call such as `addRoverCameraServo(14, -15, 30, 0, 2, 900, 2100, false, false)` is deliberately not part of the API. Several adjacent numbers and booleans are too difficult to understand or review without repeatedly consulting the function signature. +Configurations use structs. Programs assign one named field per line and then +register the completed configuration. This avoids positional lists for settings +such as angles, pulse widths, polarity, and ranges. -The public API uses named configuration structs. Field names include units where a bare number would otherwise be ambiguous, and enums replace booleans whose meaning would be unclear at the call site. +### Complete firmware -### Proposed configuration types - -The core public types are: +This program defines the standard camera tilt, headlight, and laser roles. It +also defines slider, button, number, and text accessory controls. ```cpp -enum class OutputPolarity { - ActiveHigh, - ActiveLow -}; +#include -enum class ButtonMode { - Toggle, - Momentary -}; +namespace { +constexpr uint8_t specialActionPin = 21; -struct FirmataServoOutput { - uint8_t pin; -}; +int repeatCount = 1; +String displayMessage; -struct FirmataPwmOutput { - uint8_t pin; -}; - -struct FirmataDigitalOutput { - uint8_t pin; - OutputPolarity polarity = OutputPolarity::ActiveHigh; -}; - -struct RoverCameraServoConfig { - uint8_t pin; - - float minimumAngleDegrees; - float maximumAngleDegrees; - float homeAngleDegrees = 0; - float nudgeDegrees = 2; - - uint16_t minimumPulseMicroseconds = 900; - uint16_t maximumPulseMicroseconds = 2100; - - bool allowRawPulse = false; - bool inverted = false; -}; - -struct RoverDigitalOutputConfig { - uint8_t pin; - OutputPolarity polarity = OutputPolarity::ActiveHigh; - bool initiallyOn = false; -}; - -struct SliderControlConfig { - String id; - String name; - int minimum; - int maximum; -}; - -struct ButtonControlConfig { - String id; - String name; - ButtonMode mode; -}; - -struct NumberControlConfig { - String id; - String name; - int minimum; - int maximum; -}; - -struct TextControlConfig { - String id; - String name; - size_t maximumLength; -}; -``` - -Defaults cover values that are commonly shared, but required hardware and display values remain explicit. The implementation must validate the completed struct when it is registered rather than assuming that every default-constructed object is usable. - -The API uses ordinary field assignments instead of C++ designated initializers. This keeps example sketches compatible with ESP32 Arduino toolchains that are not configured for C++20. - -### Generic-control registration - -A complete sketch for one servo slider, one light-brightness slider, and one custom momentary button is: - -```cpp -#include -#include -#include - -/* - * Controls are advertised in the order they are added to this object. The - * browser preserves that order when it renders the peripheral's column. - */ -RoverPeripheralFirmata peripheral("Example peripheral"); - -/* - * This is ordinary application code rather than Firmata plumbing. A real - * peripheral can replace it with any device-specific sequence or library call. - */ -void runSpecialAction() { - // Start or schedule the peripheral's custom behavior here. +void runSpecialAction(bool pressed) { + digitalWrite(specialActionPin, pressed ? HIGH : LOW); } -void setup() { - Firmata.begin(115200); - - /* - * roverd handles this control with standard Firmata SERVO commands. The - * ESP32 application does not need a callback for each slider update. - */ - SliderControlConfig servoPosition; - servoPosition.id = "servoPosition"; - servoPosition.name = "Servo position"; - servoPosition.minimum = 0; - servoPosition.maximum = 180; - - FirmataServoOutput servoOutput; - servoOutput.pin = 14; - - peripheral.addServoSlider(servoPosition, servoOutput); - - /* - * roverd handles this control with standard Firmata PWM commands. The range - * is included in the generated description and displayed by the web UI. - */ - SliderControlConfig lightBrightness; - lightBrightness.id = "lightBrightness"; - lightBrightness.name = "Light brightness"; - lightBrightness.minimum = 0; - lightBrightness.maximum = 255; - - FirmataPwmOutput lightOutput; - lightOutput.pin = 18; - - peripheral.addPwmSlider(lightBrightness, lightOutput); - - /* - * Custom controls are delivered through the rover-peripheral Firmata feature. - * The library finds this registration by control ID and invokes the callback - * with true on press and false on release. - */ - ButtonControlConfig specialAction; - specialAction.id = "specialAction"; - specialAction.name = "Run special action"; - specialAction.mode = ButtonMode::Momentary; - - peripheral.addButton( - specialAction, - [](bool pressed) { - if (pressed) { - runSpecialAction(); - } - } - ); - - // Register the extension with Firmata and finalize the control description. - peripheral.begin(); +void setRepeatCount(int value) { + repeatCount = value; } -void loop() { - // Standard Firmata messages and rover-peripheral SysEx messages share this parser. - while (Firmata.available()) { - Firmata.processInput(); - } - - // Let the peripheral library perform any deferred send or callback work. - peripheral.update(); +void setDisplayMessage(const String& value) { + displayMessage = value; } -``` +} // namespace -The intended generic registration methods are: +void configureRoverPeripheral(RoverPeripheral& peripheral) { + peripheral.name("Example rover peripheral"); -```cpp -addServoSlider(const SliderControlConfig&, const FirmataServoOutput&) -addPwmSlider(const SliderControlConfig&, const FirmataPwmOutput&) -addDigitalButton(const ButtonControlConfig&, const FirmataDigitalOutput&) - -addSlider(const SliderControlConfig&, SliderCallback) -addButton(const ButtonControlConfig&, ButtonCallback) -addNumber(const NumberControlConfig&, NumberCallback) -addText(const TextControlConfig&, TextCallback) -``` - -These helpers still produce only the four agreed UI types. The overload or method name distinguishes a standard Firmata output from a custom callback; it does not create an additional UI type. - -The standardized built-in replacements use separate methods because they do not create generic UI controls: - -```cpp -addRoverCameraServo(const RoverCameraServoConfig&) -addRoverHeadlight(const RoverDigitalOutputConfig&) -addRoverLaser(const RoverDigitalOutputConfig&) -``` - -A laptop GPIO peripheral can combine built-in replacements and additional controls: - -```cpp -#include -#include -#include - -RoverPeripheralFirmata peripheral("Laptop rover GPIO"); - -void setup() { - Firmata.begin(115200); - - /* - * These declarations satisfy existing rover roles. They retain the normal - * camera, headlight, and laser UI instead of entering the generic column. - */ RoverCameraServoConfig cameraServo; cameraServo.pin = 14; cameraServo.minimumAngleDegrees = -15; @@ -718,68 +539,289 @@ void setup() { cameraServo.maximumPulseMicroseconds = 2100; cameraServo.allowRawPulse = false; cameraServo.inverted = false; - - peripheral.addRoverCameraServo(cameraServo); + peripheral.addCameraServo(cameraServo); RoverDigitalOutputConfig headlight; headlight.pin = 18; headlight.polarity = OutputPolarity::ActiveHigh; headlight.initiallyOn = false; - - peripheral.addRoverHeadlight(headlight); + peripheral.addHeadlight(headlight); RoverDigitalOutputConfig laser; - laser.pin = 19; + laser.pin = 16; laser.polarity = OutputPolarity::ActiveHigh; laser.initiallyOn = false; + peripheral.addLaser(laser); - peripheral.addRoverLaser(laser); + pinMode(specialActionPin, OUTPUT); + digitalWrite(specialActionPin, LOW); - /* - * This is an additional feature, so it appears below the peripheral heading - * in the ordered generic-control column. - */ - SliderControlConfig underglowBrightness; - underglowBrightness.id = "underglowBrightness"; - underglowBrightness.name = "Underglow brightness"; - underglowBrightness.minimum = 0; - underglowBrightness.maximum = 255; + SliderControlConfig brightness; + brightness.name = "Light brightness"; + brightness.minimum = 0; + brightness.maximum = 255; - peripheral.addSlider( - underglowBrightness, - [](int brightness) { - setUnderglowBrightness(brightness); - } - ); + PwmOutput brightnessOutput; + brightnessOutput.pin = 17; - peripheral.begin(); -} + peripheral.addSlider(brightness, brightnessOutput); -void loop() { - while (Firmata.available()) { - Firmata.processInput(); - } - peripheral.update(); + ButtonControlConfig action; + action.name = "Special action"; + action.mode = ButtonMode::Momentary; + + peripheral.addButton(action, runSpecialAction); + + NumberControlConfig repeats; + repeats.name = "Repeat count"; + repeats.minimum = 1; + repeats.maximum = 20; + + peripheral.addNumber(repeats, setRepeatCount); + + TextControlConfig message; + message.name = "Display message"; + message.maximumLength = 64; + + peripheral.addText(message, setDisplayMessage); } ``` +Controls appear in registration order. Each accessory control name must be +non-empty and unique within its peripheral. The name is also its wire +identifier. + +### Built-in rover roles + +The standard roles use these configuration types: + +```cpp +RoverCameraServoConfig +RoverDigitalOutputConfig +``` + +They are registered with: + +```cpp +peripheral.addCameraServo(cameraServo); +peripheral.addHeadlight(headlight); +peripheral.addLaser(laser); +``` + +Camera servo programs set the pin, logical angle range, home angle, nudge size, +pulse range, raw-pulse policy, and inversion in +`RoverCameraServoConfig`. Headlight and laser programs set the pin, polarity, +and initial state in `RoverDigitalOutputConfig`. + +These roles keep the existing camera tilt, headlight, and laser HUD controls. +They do not add entries to the accessory list. + +### Standard accessory outputs + +A servo slider combines `SliderControlConfig` with `ServoOutput`: + +```cpp +SliderControlConfig position; +position.name = "Arm position"; +position.minimum = 0; +position.maximum = 180; + +ServoOutput servo; +servo.pin = 13; + +peripheral.addSlider(position, servo); +``` + +A PWM slider combines `SliderControlConfig` with `PwmOutput`: + +```cpp +SliderControlConfig brightness; +brightness.name = "Light brightness"; +brightness.minimum = 0; +brightness.maximum = 255; + +PwmOutput light; +light.pin = 17; + +peripheral.addSlider(brightness, light); +``` + +A digital button combines `ButtonControlConfig` with `DigitalOutput`: + +```cpp +ButtonControlConfig workLight; +workLight.name = "Work light"; +workLight.mode = ButtonMode::Toggle; + +DigitalOutput light; +light.pin = 21; +light.polarity = OutputPolarity::ActiveHigh; + +peripheral.addButton(workLight, light); +``` + +### Custom accessory functions + +A custom slider passes its value to a callback: + +```cpp +SliderControlConfig speed; +speed.name = "Motor speed"; +speed.minimum = 0; +speed.maximum = 100; + +peripheral.addSlider(speed, setMotorSpeed); +``` + +A custom button passes its logical state to a bool callback: + +```cpp +ButtonControlConfig motor; +motor.name = "Motor"; +motor.mode = ButtonMode::Momentary; + +peripheral.addButton(motor, setMotorRunning); +``` + +Momentary bool callbacks receive `true` on press and `false` on release. A +zero-argument callback may be registered for a momentary action that runs only +on press. + +Number and text inputs use their corresponding configuration structs: + +```cpp +NumberControlConfig repeats; +repeats.name = "Repeat count"; +repeats.minimum = 1; +repeats.maximum = 20; +peripheral.addNumber(repeats, setRepeatCount); + +TextControlConfig message; +message.name = "Display message"; +message.maximumLength = 64; +peripheral.addText(message, setDisplayMessage); +``` + +A program may define `updateRoverPeripheral()` for recurring work: + +```cpp +void updateRoverPeripheral() { + // Update application state. +} +``` + +Callbacks and recurring work must not block Firmata processing. Programs must +not write debug output to `Serial` because Firmata uses that stream. + +### Library runtime + +The library runtime performs these steps: + +1. opens `Serial` at 115200 baud; +2. calls `configureRoverPeripheral()`; +3. sets the serial timeout to zero; +4. initializes Firmata and the rover-peripheral feature; +5. applies initial output states; and +6. processes Firmata messages and optional recurring work. + +The zero timeout prevents ConfigurableFirmata from waiting for its receive +buffer to fill before processing a short command. + +### PlatformIO package + +The package source and reference project are: + +```text +esp32/ +├── libraries/ +│ └── RoverPeripheralFirmata/ +│ ├── library.json +│ ├── LICENSE +│ ├── README.md +│ ├── examples/ +│ └── src/ +└── rover-gpio-peripheral/ + ├── platformio.ini + └── src/main.cpp +``` + +The published package name is `legop3/RoverPeripheral`. Version `2.0.0` +contains the struct-based public API. + +A classic ESP32 PlatformIO project declares: + +```ini +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino + +lib_deps = + legop3/RoverPeripheral @ ^2.0.0 +``` + +A native-USB ESP32-S3 uses `board = esp32-s3-devkitc-1` and: + +```ini +build_flags = + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 +``` + +The package manifest installs ConfigurableFirmata, ArduinoJson, and ESP32Servo. + +Release validation and publication use: + +```bash +pio pkg pack esp32/libraries/RoverPeripheralFirmata +pio pkg publish esp32/libraries/RoverPeripheralFirmata --owner legop3 +``` + +Published versions are immutable. Each release uses a new version in +`library.json`. + +### Building and probing + +Build and upload the repository reference firmware with: + +```bash +cd esp32/rover-gpio-peripheral +pio run -e esp32dev +pio run -e esp32dev -t upload --upload-port /dev/ttyUSB0 +``` + +Use `esp32-s3-devkitc-1` and the matching `/dev/ttyACM*` device for a +native-USB ESP32-S3. + +```bash +cd pi/roverd +go run ./cmd/peripheral-probe -port /dev/ttyUSB0 +``` ## Connection lifecycle ### Startup discovery -Peripheral discovery happens exactly once per `roverd` process. Before constructing the built-in hardware controllers or connecting to the server, `roverd`: +Peripheral discovery happens exactly once per `roverd` process. The currently implemented startup path runs before `roverd` constructs its existing built-in hardware controllers or connects to the server: 1. Enumerates the serial devices present on Linux. 2. Opens each candidate device found by the startup scan. -3. Starts one Firmata client per opened connection. -4. Performs the normal Firmata firmware and capability queries. -5. Sends the rover-peripheral `DESCRIBE` operation. -6. Decodes and parses each `DESCRIPTION` response. -7. Assigns process-local IDs such as `firmata-0` and `firmata-1`. -8. Resolves the optional `cameraServo`, `headlight`, and `laser` roles. -9. Builds the fixed generic peripheral list. -10. Constructs `WSClient` with the resolved built-in controllers and peripherals. -11. Connects to the server and sends the normal rover hello. +3. Waits for a possible board reset and drains stale serial bytes to a quiet read boundary. +4. Starts one Firmata client per opened connection. +5. Performs the normal Firmata firmware and capability queries. +6. Sends the rover-peripheral `DESCRIBE` operation. +7. Decodes and validates each `DESCRIPTION` response. +8. Validates every advertised standard-output pin against Firmata capabilities. +9. Configures generic digital, PWM, and servo pin modes once. +10. Assigns process-local IDs such as `firmata-0` and `firmata-1` in discovery order. +11. Resolves `cameraServo`, `headlight`, and `laser` against the native configuration. +12. Rejects duplicate ESP32 providers only when the corresponding native role is disabled and Firmata selection would otherwise be ambiguous. +13. Constructs the selected native or Firmata controllers and fixed generic inventory. +14. Constructs `WSClient` with those controllers and that inventory. +15. Connects to the server and includes the effective built-in configuration and renderable inventory in the normal rover hello. + +The Linux scan checks stable `/dev/serial/by-id/*` names first, then `/dev/ttyUSB*` and `/dev/ttyACM*`. It canonicalizes symlinks so one device is not opened twice under its stable name and kernel name, and it excludes the configured Roomba Open Interface serial device. Each opened candidate receives the same reset wait used by the probe, followed by a read-until-quiet drain so an old partial SysEx cannot contaminate the new handshake. + +Firmware and capability queries remain standard Firmata. `RoverPeripheralFirmata::begin()` registers the standard Firmata firmware name `RoverPeripheralFirmata`, so individual sketches do not repeat that discovery detail. A Firmata device with another firmware name is closed and ignored. Once a device identifies itself as rover-peripheral firmware, a malformed capability or description response is a startup error rather than a silently missing configured accessory. Linux paths such as `/dev/ttyACM0` remain private `roverd` connection details. The browser and server use only the process-local peripheral ID from the hello. @@ -802,8 +844,20 @@ If an ESP32 is unplugged or its serial connection fails while `roverd` is runnin 1. Its Firmata client marks the connection unavailable. 2. Commands routed to that peripheral or one of its built-in roles return an error. -3. `roverd` logs that the peripheral requires reconnection followed by a restart. -4. The advertised roster and visible controls do not change during that process lifetime. +3. The failed command is logged by the existing rover WebSocket command path. +4. The advertised inventory does not change during that process lifetime. + +The first terminal read or write error also produces one concise `tty1` +broadcast through the existing `ConsoleNotifier`: + +```text +Rover peripheral "Rover GPIO" (firmata-0) disconnected: . Reconnect it and restart roverd. +``` + +Normal startup uses the same local-console mechanism to announce every fixed +peripheral, the selected native or ESP32 backend for camera servo, headlight, +and laser, any ignored ESP32 roles, or that no ESP32 was found. Detailed Linux +paths and Firmata handshake diagnostics remain in the systemd journal. The disconnected device is never replaced automatically by another serial device. This ensures that a command cannot be redirected merely because Linux reused a `/dev/ttyACM*` path. @@ -870,10 +924,12 @@ No global `session.features` flag is necessary. Peripherals are inherently optio ## Browser-to-server control path -The browser sends one generic Socket.IO event for every peripheral control: +The browser sends every peripheral interaction through the existing Socket.IO +`command` event. Peripheral actuation is a rover command, so it does not need a +parallel event or authorization path. ```text -peripheral:set +command ``` Payload: @@ -881,9 +937,14 @@ Payload: ```json { "roverId": "rover-name", - "peripheralId": "firmata-0", - "controlId": "servoPosition", - "value": 90 + "type": "peripheral", + "data": { + "peripheral": { + "id": "firmata-0", + "control": "Servo position", + "value": 90 + } + } } ``` @@ -900,7 +961,7 @@ If the socket cannot drive that rover, the event acknowledgement returns an erro "type": "peripheral", "peripheral": { "id": "firmata-0", - "control": "servoPosition", + "control": "Servo position", "value": 90 } } @@ -960,10 +1021,10 @@ A toggle sends one of the last two writes per activation. A momentary button sen ## Custom callback communication -Suppose `specialAction` is pressed. `roverd` creates the JSON payload: +Suppose `Special action` is pressed. `roverd` creates the JSON payload: ```json -{"control":"specialAction","value":true} +{"control":"Special action","value":true} ``` After 8-to-7-bit encoding, it is placed in: @@ -981,19 +1042,18 @@ The ESP32 library: 1. Receives the SysEx feature message through Firmata. 2. Decodes the JSON bytes. 3. Reads `control` and `value`. -4. Finds the control registered as `specialAction`. +4. Finds the control registered as `Special action`. 5. Converts the JSON boolean to the registered button callback's `bool` argument. 6. Calls the callback with `true`. On release the same path carries `false`. -For the example sketch, only the press runs the one-shot function: +For the reference sketch, the callback drives its output high on press and low +again on release: ```cpp -[](bool pressed) { - if (pressed) { - runSpecialAction(); - } +void runSpecialAction(bool pressed) { + digitalWrite(specialActionPin, pressed ? HIGH : LOW); } ``` @@ -1034,9 +1094,17 @@ The generic renderer maps: - `number` to a labeled numeric input. - `text` to a labeled single-line text input. -The same generic component is reused by desktop and mobile layouts. Layout wrappers decide where the column appears; device-specific components are not created for individual peripherals. +Generic peripheral controls are rover controls, so they follow the new driver's HUD language. They do not belong in either sidebar: the sidebars contain chat, queues, room controls, settings, and other controls that are not direct rover actuation. -Control values are local UI values in the first implementation. Slider and toggle changes update the displayed value immediately and are then sent to the server. Restarting `roverd` recreates controls from the new hello rather than persisting peripheral values in `roverSettings`. +The standardized replacements do not create any new UI. `cameraServo`, `headlight`, and `laser` continue to use their current camera-tilt, headlight, and laser HUD controls. Only entries in the generic `controls` arrays appear in a new surface named `Accessories`. + +On desktop, `Accessories` is a vertical button centered on the left wall of the video. It uses the existing translucent black HUD treatment and opens a height-limited, vertically scrollable panel toward the right. The panel uses the same compact control renderer as mobile and is independent of the bottom-left horn, headlight, and laser pod. + +On mobile, a vertical `Accessories` button sits directly to the right of the vacuum-forward and vacuum-backward buttons. Activating it replaces the complete `AuxColumn` contents with the ordered, vertically scrollable accessory list. A small `Aux` button shares the first compact device heading and returns to the normal vacuum, camera, light, laser, and horn controls without creating a separate rail or overlay border. + +Desktop and mobile reuse one placement-independent `RoverAccessoryControls` renderer inside their different containers. Device-specific React components are not created for individual peripherals. The renderer sends actions through `ControlSystemProvider`, `ControlContext`, and the existing command pipeline so assignment gating, input cancellation, and command behavior remain consistent with other rover HUD controls. Both parents and the renderer disappear completely when the assigned rover has no generic controls; no launcher, empty shell, or reserved space remains. + +Control values are local UI values in the first implementation. Slider and toggle changes update the displayed value immediately and are then sent to the server. Generic sliders use the same custom pointer-capture approach as mobile camera tilt rather than a browser-native range control, which keeps touch behavior and appearance consistent while driving. Restarting `roverd` recreates controls from the new hello rather than persisting peripheral values in `roverSettings`. ## Permissions @@ -1046,7 +1114,7 @@ The server enforces this with the existing `roverManager.canDrive(roverId, socke No peripheral-specific roles, administrator-only controls, access lists, or permissions in ESP32 configuration are part of this design. -When the driver loses the rover assignment, the UI stops presenting enabled controls and subsequent `peripheral:set` requests fail the same server-side drive check. +When the driver loses the rover assignment, the UI stops presenting enabled controls and subsequent peripheral commands fail the same server-side drive check. ## Expected repository changes @@ -1054,7 +1122,7 @@ Implementation should remain concentrated in a few clear areas. ### ESP32 library -Create a small Arduino-compatible `RoverPeripheralFirmata` library containing: +The Arduino-compatible `RoverPeripheral` package now contains: - Ordered control registration. - Standardized `cameraServo`, `headlight`, and `laser` role registration. @@ -1063,15 +1131,24 @@ Create a small Arduino-compatible `RoverPeripheralFirmata` library containing: - `DESCRIBE` response handling. - `CONTROL` decoding and callback dispatch. - 8-to-7-bit payload encoding and decoding. -- The standard-output and custom-control helper methods listed above. +- The public configuration structs and registration methods listed above. +- Arduino `setup()` and `loop()` ownership, including the zero-timeout Firmata + parser configuration required for immediate short-command handling. -Example ESP32 sketches should use this library rather than hand-writing SysEx parsing. +Example ESP32 sketches import only `RoverPeripheral.h` rather than exposing or +hand-writing any Firmata setup or SysEx parsing. + +The package source lives in `esp32/libraries/RoverPeripheralFirmata`, with the +complete `esp32/rover-gpio-peripheral` PlatformIO project serving as the +repository reference firmware. The package manifest, README, and examples are +self-contained so the same directory can be published directly to the +PlatformIO Registry as `legop3/RoverPeripheral`. ### `pi/roverd` -Add a peripheral manager responsible for: +`PeripheralManager` is responsible for: -- One-time Linux USB serial discovery during startup. +- One-time Linux USB serial discovery during startup on either rover host type. - One Firmata client per connected peripheral. - Firmata handshake and capability queries. - Rover-peripheral description queries. @@ -1081,9 +1158,15 @@ Add a peripheral manager responsible for: - Custom `CONTROL` dispatch. - Rejecting commands for disconnected peripheral IDs. +The manager validates every advertised standard-output pin against the device's Firmata capability response and configures each generic pin mode once during startup. Runtime servo and PWM changes therefore send only value writes; they do not repeatedly detach and reconfigure the hardware output. Generic digital controls start logically off, including the corresponding high electrical level for active-low declarations. + +Only renderable control metadata leaves `roverd`. Firmata pin numbers, output mappings, Linux paths, live clients, capabilities, and built-in-role declarations stay in the manager's private fixed inventory. The rover hello contains process-local peripheral IDs, names, and ordered generic controls. + The manager should remain independent of the existing Roomba Open Interface serial adapter. A peripheral serial connection is not the Roomba base serial connection and must not be routed through `SerialAdapter`. -Refactor camera servo and GPIO toggles so `WSClient` depends on the shared controller interfaces rather than platform-selected concrete types. Preserve the existing logical servo movement and toggle-state behavior above the Pi and Firmata physical writers. +The transport foundation is a focused Firmata parser/client in `pi/roverd/firmata.go`. It operates on `io.ReadWriteCloser`, which keeps byte-stream behavior testable without hardware and lets discovery pass either `/dev/ttyUSB*` or `/dev/ttyACM*` ports into the same client. `pi/roverd/cmd/peripheral-probe` remains the direct hardware diagnostic entry point, while `PeripheralManager` now connects the same client to automatic daemon startup discovery. + +`WSClient` now depends on shared camera-servo and toggle controller interfaces rather than platform-selected concrete types. The startup resolver uses the same native-first rule in the ARM Pi and amd64 laptop binaries. Firmata camera movement retains the established limits, home position, nudging, inversion, pulse calibration, raw-pulse policy, and movement-rate behavior; Firmata toggles retain logical state and polarity conversion. ### Server @@ -1092,7 +1175,7 @@ Extend the existing rover connection and roster path to: - Accept `peripherals` in rover hello metadata. - Include peripherals in `roverManager.getRoster()`. - Continue exposing effective `cameraServo`, `headlight`, and `laser` metadata through their existing roster fields regardless of physical backend. -- Add the generic `peripheral:set` Socket.IO handler. +- Route generic controls through the existing Socket.IO `command` handler. - Reuse `roverManager.canDrive()` for authorization. - Forward the command through `commandService` so rover acknowledgements remain consistent with other controls. @@ -1103,26 +1186,24 @@ Add one generic peripheral control renderer that: - Selects the assigned rover and its peripherals from session state. - Preserves peripheral and control array order. - Renders only the four agreed control types. -- Sends every interaction through the same `peripheral:set` event. +- Sends every interaction through the existing `command` event with type `peripheral`. - Supports momentary press and release for pointer, touch, and keyboard activation. -- Fits into the existing desktop and mobile driver control layouts. +- Mounts in the desktop left-wall expansion and as a replacement view inside mobile `AuxColumn`. +- Uses the shared control context and command pipeline rather than emitting directly from layout code. - Disappears completely when the assigned rover has no peripherals. ## Implementation sequence -The smallest useful vertical implementation is: +The implemented vertical path is: -1. Build the ESP32 Firmata feature and the three-control example sketch. -2. Add boot-time one-device USB discovery and Firmata communication to `roverd`. -3. Include the fixed description in the rover hello and server roster. -4. Render the ordered generic controls in the driver UI. -5. Route generic servo and PWM controls through standard Firmata. -6. Route the generic momentary button through the custom callback operation. -7. Add the standardized ESP32 camera-servo, headlight, and laser declarations. -8. Refactor built-in controllers to select native Pi or Firmata backends at startup. -9. Verify that existing tilt, headlight, laser, keybinding, gamepad, state-event, and server-policy behavior is unchanged with both backends. -10. Generalize startup discovery from one connection to multiple simultaneous peripherals. -11. Add the remaining toggle, number, and text registration helpers and UI renderers. +1. The public ESP32 package declares built-in roles and ordered accessory controls. +2. Its private Firmata implementation advertises the generated description. +3. `roverd` discovers all startup peripherals and resolves hardware backends. +4. Rover hello metadata carries the fixed renderable inventory to the server. +5. The server preserves that inventory in the roster and applies normal driver authorization. +6. The shared web renderer presents the four control types on desktop and mobile. +7. Commands return through the existing pipeline to standard Firmata outputs or custom callbacks. +8. The package README and examples give external authors the same concise API used by the repository firmware. The protocol and session shapes are arrays from the beginning, so supporting multiple devices does not require changing the external contracts after the first-device vertical slice. @@ -1141,16 +1222,17 @@ The completed system should be verified with a real ESP32 and rover Linux comput ### Standard controls -- Move the servo slider and confirm pin 14 receives servo values across the declared range. -- Move the brightness slider and confirm pin 18 receives PWM values across the declared range. +- Move the reference brightness slider and confirm pin 17 receives PWM values across the declared range. +- Register a `SliderControlConfig` with a `ServoOutput` and confirm its selected pin receives servo values across the declared range. - Confirm neither standard control invokes the custom callback path. ### Custom controls - Press the momentary button and confirm the ESP32 callback receives `true` once. - Release it and confirm the callback receives `false` once. +- Submit number and text values and confirm their typed callbacks receive the advertised values. - Cancel a held pointer or leave the control layout and confirm a release is sent. -- Confirm arbitrary non-blocking ESP32 behavior can continue from `loop()` after the callback changes its state. +- Confirm arbitrary non-blocking ESP32 behavior can continue from `updateRoverPeripheral()` after the callback changes its state. ### Permissions @@ -1160,7 +1242,7 @@ The completed system should be verified with a real ESP32 and rover Linux comput ### Built-in GPIO replacement -- Start a laptop rover with native camera servo, headlight, and laser disabled and an ESP32 declaring all three roles. +- Start either rover host type with native camera servo, headlight, and laser disabled and an ESP32 declaring all three roles. - Confirm the normal camera tilt, headlight, and laser UI appears without generic duplicates. - Confirm camera angle limits, home position, nudge amount, inversion, pulse calibration, and rate limiting match the declared ESP32 configuration. - Confirm headlight and laser toggle state events remain identical to the native Pi path. @@ -1168,6 +1250,7 @@ The completed system should be verified with a real ESP32 and rover Linux comput - Enable a native role and declare the same ESP32 role; confirm native wins and the ignored role is logged. - Disable native and declare the same role from two ESP32s; confirm startup fails with a clear duplicate-role error. - Confirm a Pi rover can use native built-in controls and generic ESP32 controls simultaneously. +- Repeat ESP32 role selection on both the ARM Pi binary and amd64 laptop binary and confirm their commands and advertised configurations match. ### Fixed-device lifecycle diff --git a/esp32/libraries/RoverPeripheralFirmata/LICENSE b/esp32/libraries/RoverPeripheralFirmata/LICENSE new file mode 100644 index 00000000..bf9bcefb --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Daniel Roberts + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/esp32/libraries/RoverPeripheralFirmata/README.md b/esp32/libraries/RoverPeripheralFirmata/README.md new file mode 100644 index 00000000..30d9f9e4 --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/README.md @@ -0,0 +1,218 @@ +# RoverPeripheral + +RoverPeripheral is an ESP32 Arduino library for MultiRoombaRover peripherals. +The ESP32 reports its built-in rover roles and accessory controls to `roverd` +over USB serial. + +## PlatformIO installation + +Classic ESP32 DevKitC-style board: + +```ini +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino + +lib_deps = + legop3/RoverPeripheral @ ^2.0.0 +``` + +Native-USB ESP32-S3 DevKitC: + +```ini +[env:esp32-s3-devkitc-1] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +build_flags = + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 + +lib_deps = + legop3/RoverPeripheral @ ^2.0.0 +``` + +## Program structure + +Include `RoverPeripheral.h` and define `configureRoverPeripheral()`: + +```cpp +#include + +void configureRoverPeripheral(RoverPeripheral& peripheral) { + peripheral.name("Headlight controller"); + + RoverDigitalOutputConfig headlight; + headlight.pin = 18; + headlight.polarity = OutputPolarity::ActiveHigh; + headlight.initiallyOn = false; + peripheral.addHeadlight(headlight); +} +``` + +The library provides `setup()` and `loop()`. Do not define them in the +peripheral program. + +## Built-in rover roles + +Camera tilt: + +```cpp +RoverCameraServoConfig cameraServo; +cameraServo.pin = 14; +cameraServo.minimumAngleDegrees = -15; +cameraServo.maximumAngleDegrees = 30; +cameraServo.homeAngleDegrees = 0; +cameraServo.nudgeDegrees = 2; +cameraServo.minimumPulseMicroseconds = 900; +cameraServo.maximumPulseMicroseconds = 2100; +cameraServo.allowRawPulse = false; +cameraServo.inverted = false; +peripheral.addCameraServo(cameraServo); +``` + +Headlight or laser: + +```cpp +RoverDigitalOutputConfig headlight; +headlight.pin = 18; +headlight.polarity = OutputPolarity::ActiveHigh; +headlight.initiallyOn = false; +peripheral.addHeadlight(headlight); + +RoverDigitalOutputConfig laser; +laser.pin = 16; +laser.polarity = OutputPolarity::ActiveHigh; +laser.initiallyOn = false; +peripheral.addLaser(laser); +``` + +These registrations use the existing camera, headlight, and laser controls in +the rover UI. They do not create accessory controls. + +## Accessory controls + +Controls appear in registration order. Each control name must be unique within +the peripheral. The name is also used as the control identifier. + +### Servo slider + +```cpp +SliderControlConfig position; +position.name = "Arm position"; +position.minimum = 0; +position.maximum = 180; + +ServoOutput servo; +servo.pin = 13; + +peripheral.addSlider(position, servo); +``` + +### PWM slider + +```cpp +SliderControlConfig brightness; +brightness.name = "Light brightness"; +brightness.minimum = 0; +brightness.maximum = 255; + +PwmOutput light; +light.pin = 17; + +peripheral.addSlider(brightness, light); +``` + +### Digital button + +```cpp +ButtonControlConfig workLight; +workLight.name = "Work light"; +workLight.mode = ButtonMode::Toggle; + +DigitalOutput light; +light.pin = 21; +light.polarity = OutputPolarity::ActiveHigh; + +peripheral.addButton(workLight, light); +``` + +### Custom slider + +```cpp +void setMotorSpeed(int value) { + // Apply value to the device. +} + +SliderControlConfig speed; +speed.name = "Motor speed"; +speed.minimum = 0; +speed.maximum = 100; + +peripheral.addSlider(speed, setMotorSpeed); +``` + +### Custom button + +```cpp +void setMotorRunning(bool running) { + // Start or stop the device. +} + +ButtonControlConfig motor; +motor.name = "Motor"; +motor.mode = ButtonMode::Momentary; + +peripheral.addButton(motor, setMotorRunning); +``` + +A momentary bool callback receives `true` on press and `false` on release. A +zero-argument callback can be used for a one-shot momentary action. + +### Number input + +```cpp +void setRepeatCount(int value) { + // Store or apply value. +} + +NumberControlConfig repeats; +repeats.name = "Repeat count"; +repeats.minimum = 1; +repeats.maximum = 20; + +peripheral.addNumber(repeats, setRepeatCount); +``` + +### Text input + +```cpp +void setDisplayMessage(const String& value) { + // Store or display value. +} + +TextControlConfig message; +message.name = "Display message"; +message.maximumLength = 64; + +peripheral.addText(message, setDisplayMessage); +``` + +## Recurring work + +Define `updateRoverPeripheral()` when the program needs recurring non-blocking +work: + +```cpp +void updateRoverPeripheral() { + // Update a state machine or device. +} +``` + +Callbacks and `updateRoverPeripheral()` must not block serial processing. +`Serial` is reserved for Firmata and must not be used for debug output. + +## License + +MIT diff --git a/esp32/libraries/RoverPeripheralFirmata/examples/CompletePeripheral/CompletePeripheral.ino b/esp32/libraries/RoverPeripheralFirmata/examples/CompletePeripheral/CompletePeripheral.ino new file mode 100644 index 00000000..3d4a33d6 --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/examples/CompletePeripheral/CompletePeripheral.ino @@ -0,0 +1,76 @@ +#include + +namespace { +constexpr uint8_t kActionPin = 21; + +int repeatCount = 1; +String displayMessage; + +void setActionActive(bool pressed) { + digitalWrite(kActionPin, pressed ? HIGH : LOW); +} + +void setRepeatCount(int value) { + repeatCount = value; +} + +void setDisplayMessage(const String& value) { + displayMessage = value; +} +} // namespace + +void configureRoverPeripheral(RoverPeripheral& io) { + io.name("Complete rover peripheral"); + + RoverCameraServoConfig cameraServo; + cameraServo.pin = 14; + cameraServo.minimumAngleDegrees = -15; + cameraServo.maximumAngleDegrees = 30; + cameraServo.homeAngleDegrees = 0; + cameraServo.nudgeDegrees = 2; + cameraServo.minimumPulseMicroseconds = 900; + cameraServo.maximumPulseMicroseconds = 2100; + cameraServo.allowRawPulse = false; + cameraServo.inverted = false; + io.addCameraServo(cameraServo); + + RoverDigitalOutputConfig headlight; + headlight.pin = 18; + headlight.polarity = OutputPolarity::ActiveHigh; + headlight.initiallyOn = false; + io.addHeadlight(headlight); + + RoverDigitalOutputConfig laser; + laser.pin = 16; + laser.polarity = OutputPolarity::ActiveHigh; + laser.initiallyOn = false; + io.addLaser(laser); + + pinMode(kActionPin, OUTPUT); + digitalWrite(kActionPin, LOW); + + SliderControlConfig brightness; + brightness.name = "Light brightness"; + brightness.minimum = 0; + brightness.maximum = 255; + + PwmOutput brightnessOutput; + brightnessOutput.pin = 17; + io.addSlider(brightness, brightnessOutput); + + ButtonControlConfig action; + action.name = "Special action"; + action.mode = ButtonMode::Momentary; + io.addButton(action, setActionActive); + + NumberControlConfig repeats; + repeats.name = "Repeat count"; + repeats.minimum = 1; + repeats.maximum = 20; + io.addNumber(repeats, setRepeatCount); + + TextControlConfig message; + message.name = "Display message"; + message.maximumLength = 64; + io.addText(message, setDisplayMessage); +} diff --git a/esp32/libraries/RoverPeripheralFirmata/examples/MinimalPeripheral/MinimalPeripheral.ino b/esp32/libraries/RoverPeripheralFirmata/examples/MinimalPeripheral/MinimalPeripheral.ino new file mode 100644 index 00000000..8a978806 --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/examples/MinimalPeripheral/MinimalPeripheral.ino @@ -0,0 +1,11 @@ +#include + +void configureRoverPeripheral(RoverPeripheral& io) { + io.name("Headlight controller"); + + RoverDigitalOutputConfig headlight; + headlight.pin = 18; + headlight.polarity = OutputPolarity::ActiveHigh; + headlight.initiallyOn = false; + io.addHeadlight(headlight); +} diff --git a/esp32/libraries/RoverPeripheralFirmata/library.json b/esp32/libraries/RoverPeripheralFirmata/library.json new file mode 100644 index 00000000..ab1cf12a --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/library.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json", + "name": "RoverPeripheral", + "version": "2.0.1", + "description": "Create self-describing ESP32 hardware controls for MultiRoombaRover", + "keywords": [ + "esp32", + "firmata", + "robotics", + "rover" + ], + "repository": { + "type": "git", + "url": "https://github.com/legop3/MultiRoombaRover.git" + }, + "homepage": "https://github.com/legop3/MultiRoombaRover/tree/main/esp32/libraries/RoverPeripheralFirmata", + "license": "MIT", + "frameworks": "arduino", + "platforms": "espressif32", + "headers": "RoverPeripheral.h", + "dependencies": { + "ConfigurableFirmata": "https://github.com/firmata/ConfigurableFirmata.git#3.2.0", + "bblanchon/ArduinoJson": "^7.4.2", + "madhephaestus/ESP32Servo": "^3.0.8" + } +} diff --git a/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.cpp b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.cpp new file mode 100644 index 00000000..83e07fef --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.cpp @@ -0,0 +1,78 @@ +#include "RoverPeripheral.h" + +#include "internal/RoverPeripheralFirmata.h" + +RoverPeripheral::RoverPeripheral() + : implementation_(new RoverPeripheralFirmata("Rover peripheral")) {} + +RoverPeripheral::~RoverPeripheral() { + delete implementation_; +} + +void RoverPeripheral::name(const String& peripheralName) { + implementation_->setName(peripheralName); +} + +void RoverPeripheral::addCameraServo(const RoverCameraServoConfig& config) { + implementation_->addRoverCameraServo(config); +} + +void RoverPeripheral::addHeadlight(const RoverDigitalOutputConfig& config) { + implementation_->addRoverHeadlight(config); +} + +void RoverPeripheral::addLaser(const RoverDigitalOutputConfig& config) { + implementation_->addRoverLaser(config); +} + +void RoverPeripheral::addSlider(const SliderControlConfig& config, const ServoOutput& output) { + implementation_->addServoSlider(config, output); +} + +void RoverPeripheral::addSlider(const SliderControlConfig& config, const PwmOutput& output) { + implementation_->addPwmSlider(config, output); +} + +void RoverPeripheral::addButton(const ButtonControlConfig& config, const DigitalOutput& output) { + implementation_->addDigitalButton(config, output); +} + +void RoverPeripheral::addSlider(const SliderControlConfig& config, SliderCallback callback) { + implementation_->addSlider(config, callback); +} + +void RoverPeripheral::addButton(const ButtonControlConfig& config, ButtonCallback callback) { + implementation_->addButton(config, callback); +} + +void RoverPeripheral::addButton(const ButtonControlConfig& config, ActionCallback callback) { + // One-shot callbacks apply only to momentary buttons. A toggle requires the + // bool callback overload because application code must receive its new state. + if (config.mode != ButtonMode::Momentary) { + abort(); + } + implementation_->addButton( + config, + [callback](bool pressed) { + if (pressed && callback) { + callback(); + } + } + ); +} + +void RoverPeripheral::addNumber(const NumberControlConfig& config, NumberCallback callback) { + implementation_->addNumber(config, callback); +} + +void RoverPeripheral::addText(const TextControlConfig& config, TextCallback callback) { + implementation_->addText(config, callback); +} + +void RoverPeripheral::begin(FirmataExt& extension) { + implementation_->begin(extension); +} + +void RoverPeripheral::update() { + implementation_->update(); +} diff --git a/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.h b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.h new file mode 100644 index 00000000..80d2713e --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheral.h @@ -0,0 +1,156 @@ +#pragma once + +#include + +#include + +/** Describes whether a logical on value drives an output pin high or low. */ +enum class OutputPolarity { + ActiveHigh, + ActiveLow, +}; + +/** Selects whether a button retains its state or is active only while held. */ +enum class ButtonMode { + Toggle, + Momentary, +}; + +/** Configuration for the rover's existing camera-tilt control. */ +struct RoverCameraServoConfig { + uint8_t pin = 0; + float minimumAngleDegrees = -15; + float maximumAngleDegrees = 30; + float homeAngleDegrees = 0; + float nudgeDegrees = 2; + uint16_t minimumPulseMicroseconds = 900; + uint16_t maximumPulseMicroseconds = 2100; + bool allowRawPulse = false; + bool inverted = false; +}; + +/** Configuration for the rover's existing headlight or laser control. */ +struct RoverDigitalOutputConfig { + uint8_t pin = 0; + OutputPolarity polarity = OutputPolarity::ActiveHigh; + bool initiallyOn = false; +}; + +/** Shared display and range settings for a slider control. */ +struct SliderControlConfig { + String name; + int minimum = 0; + int maximum = 100; +}; + +/** Shared display and interaction settings for a button control. */ +struct ButtonControlConfig { + String name; + ButtonMode mode = ButtonMode::Momentary; +}; + +/** Shared display and range settings for a number input. */ +struct NumberControlConfig { + String name; + int minimum = 0; + int maximum = 100; +}; + +/** Shared display and length settings for a text input. */ +struct TextControlConfig { + String name; + size_t maximumLength = 32; +}; + +/** Selects a standard Firmata servo as the destination for a slider. */ +struct ServoOutput { + uint8_t pin = 0; +}; + +/** Selects an ESP32 PWM pin as the destination for a slider. */ +struct PwmOutput { + uint8_t pin = 0; +}; + +/** Selects an ESP32 digital pin as the destination for a button. */ +struct DigitalOutput { + uint8_t pin = 0; + OutputPolarity polarity = OutputPolarity::ActiveHigh; +}; + +using SliderCallback = std::function; +using ButtonCallback = std::function; +using ActionCallback = std::function; +using NumberCallback = std::function; +using TextCallback = std::function; + +class FirmataExt; +class RoverPeripheralFirmata; + +/** + * Registration API for a self-describing rover peripheral. + * + * A sketch constructs each configuration one field at a time and registers it + * in configureRoverPeripheral(). Serial and protocol setup stay in the library. + */ +class RoverPeripheral { + public: + RoverPeripheral(); + ~RoverPeripheral(); + + RoverPeripheral(const RoverPeripheral&) = delete; + RoverPeripheral& operator=(const RoverPeripheral&) = delete; + + /** Sets the peripheral name shown above its accessory controls. */ + void name(const String& peripheralName); + + /** Registers the rover's existing camera-tilt control. */ + void addCameraServo(const RoverCameraServoConfig& config); + + /** Registers the rover's existing headlight control. */ + void addHeadlight(const RoverDigitalOutputConfig& config); + + /** Registers the rover's existing laser control. */ + void addLaser(const RoverDigitalOutputConfig& config); + + /** Registers a slider backed by a standard Firmata servo output. */ + void addSlider(const SliderControlConfig& config, const ServoOutput& output); + + /** Registers a slider backed by an ESP32 PWM output. */ + void addSlider(const SliderControlConfig& config, const PwmOutput& output); + + /** Registers a button backed by an ESP32 digital output. */ + void addButton(const ButtonControlConfig& config, const DigitalOutput& output); + + /** Registers a slider handled by application code. */ + void addSlider(const SliderControlConfig& config, SliderCallback callback); + + /** Registers a button whose callback receives its logical state. */ + void addButton(const ButtonControlConfig& config, ButtonCallback callback); + + /** Registers a momentary button whose callback runs only on press. */ + void addButton(const ButtonControlConfig& config, ActionCallback callback); + + /** Registers a number input handled by application code. */ + void addNumber(const NumberControlConfig& config, NumberCallback callback); + + /** Registers a text input handled by application code. */ + void addText(const TextControlConfig& config, TextCallback callback); + + private: + // The implementation is opaque so importing this header does not expose any + // Firmata types or require firmware authors to understand the wire protocol. + RoverPeripheralFirmata* implementation_; + + void begin(FirmataExt& extension); + void update(); + + friend void setup(); + friend void loop(); +}; + +/** Called once by the library after Arduino and Serial initialization. */ +void configureRoverPeripheral(RoverPeripheral& peripheral); + +/** Optional non-blocking hook for recurring application work. */ +void updateRoverPeripheral(); diff --git a/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheralRuntime.cpp b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheralRuntime.cpp new file mode 100644 index 00000000..f0318087 --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/src/RoverPeripheralRuntime.cpp @@ -0,0 +1,46 @@ +#include "RoverPeripheral.h" + +#include +#include + +namespace { +FirmataExt firmataExtension; +RoverPeripheral peripheral; +} // namespace + +// A weak no-op preserves the zero-boilerplate case while allowing a sketch to +// define the same function when animations or state machines need regular work. +void __attribute__((weak)) updateRoverPeripheral() {} + +void setup() { + // The public configuration hook runs after Arduino initialization, allowing + // peripheral code to safely use pinMode() and initialize third-party devices. + Serial.begin(115200); + configureRoverPeripheral(peripheral); + + // ConfigurableFirmata batches reads on ESP32-class boards. Arduino's default + // one-second Stream timeout would delay short commands while waiting for the + // batch buffer to fill, so consume only bytes that have already arrived. + Serial.setTimeout(0); + Firmata.begin(Serial); + peripheral.begin(firmataExtension); + + // Applying a normal Firmata reset after registration establishes every + // declared initial output and makes the first host connection deterministic. + Firmata.parse(SYSTEM_RESET); +} + +void loop() { + // ConfigurableFirmata retains partial parser state between iterations. Stop + // after each complete message so user update work cannot be starved by a + // sustained burst, while ordinary short commands are still drained at once. + while (Firmata.available()) { + Firmata.processInput(); + if (!Firmata.isParsingMessage()) { + break; + } + } + + peripheral.update(); + updateRoverPeripheral(); +} diff --git a/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.cpp b/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.cpp new file mode 100644 index 00000000..37abc09f --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.cpp @@ -0,0 +1,521 @@ +#include "RoverPeripheralFirmata.h" + +namespace { +constexpr byte kPeripheralFeature = 0x01; +constexpr byte kDescribeOperation = 0x00; +constexpr byte kDescriptionOperation = 0x01; +constexpr byte kControlOperation = 0x02; + +const char* buttonModeName(ButtonMode mode) { + return mode == ButtonMode::Toggle ? "toggle" : "momentary"; +} + +const char* outputTypeName(uint8_t value) { + switch (value) { + case 0: + return "servo"; + case 1: + return "pwm"; + case 2: + return "digital"; + default: + return "custom"; + } +} +} // namespace + +RoverPeripheralFirmata* RoverPeripheralFirmata::instance_ = nullptr; + +RoverPeripheralFirmata::RoverPeripheralFirmata(const String& name) : name_(name) {} + +void RoverPeripheralFirmata::setName(const String& name) { + if (name.length() == 0) { + // A blank heading makes multiple attached peripherals impossible to + // distinguish. Treat it as a firmware-authoring error at startup rather + // than advertising ambiguous controls to the rover. + abort(); + } + name_ = name; +} + +void RoverPeripheralFirmata::validateControlName(const String& name) const { + if (name.length() == 0) { + // Registration errors are programmer errors discovered during setup. A + // hard stop is preferable to advertising a partially usable device whose + // behavior depends on which malformed control the driver touches first. + abort(); + } + for (const ControlRegistration& existing : controls_) { + if (existing.id == name) { + abort(); + } + } +} + +void RoverPeripheralFirmata::validateRange(const String& name, int minimum, int maximum) const { + if (name.length() == 0 || minimum > maximum) { + abort(); + } +} + +void RoverPeripheralFirmata::addServoSlider(const SliderControlConfig& config, const ServoOutput& output) { + validateControlName(config.name); + validateRange(config.name, config.minimum, config.maximum); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Slider; + control.output = OutputType::Servo; + control.minimum = config.minimum; + control.maximum = config.maximum; + control.pin = output.pin; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addPwmSlider(const SliderControlConfig& config, const PwmOutput& output) { + validateControlName(config.name); + validateRange(config.name, config.minimum, config.maximum); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Slider; + control.output = OutputType::Pwm; + control.minimum = config.minimum; + control.maximum = config.maximum; + control.pin = output.pin; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addDigitalButton(const ButtonControlConfig& config, const DigitalOutput& output) { + validateControlName(config.name); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Button; + control.output = OutputType::Digital; + control.buttonMode = config.mode; + control.pin = output.pin; + control.polarity = output.polarity; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addSlider(const SliderControlConfig& config, SliderCallback callback) { + validateControlName(config.name); + validateRange(config.name, config.minimum, config.maximum); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Slider; + control.output = OutputType::Custom; + control.minimum = config.minimum; + control.maximum = config.maximum; + control.sliderCallback = callback; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addButton(const ButtonControlConfig& config, ButtonCallback callback) { + validateControlName(config.name); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Button; + control.output = OutputType::Custom; + control.buttonMode = config.mode; + control.buttonCallback = callback; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addNumber(const NumberControlConfig& config, NumberCallback callback) { + validateControlName(config.name); + validateRange(config.name, config.minimum, config.maximum); + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Number; + control.output = OutputType::Custom; + control.minimum = config.minimum; + control.maximum = config.maximum; + control.numberCallback = callback; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addText(const TextControlConfig& config, TextCallback callback) { + validateControlName(config.name); + if (config.maximumLength == 0) { + abort(); + } + ControlRegistration control; + control.id = config.name; + control.name = config.name; + control.type = ControlType::Text; + control.output = OutputType::Custom; + control.maximumLength = config.maximumLength; + control.textCallback = callback; + controls_.push_back(control); +} + +void RoverPeripheralFirmata::addRoverCameraServo(const RoverCameraServoConfig& config) { + cameraServo_ = config; + hasCameraServo_ = true; +} + +void RoverPeripheralFirmata::addRoverHeadlight(const RoverDigitalOutputConfig& config) { + headlight_ = config; + hasHeadlight_ = true; +} + +void RoverPeripheralFirmata::addRoverLaser(const RoverDigitalOutputConfig& config) { + laser_ = config; + hasLaser_ = true; +} + +void RoverPeripheralFirmata::begin(FirmataExt& extension) { + if (instance_ != nullptr && instance_ != this) { + abort(); + } + instance_ = this; + extension.addFeature(*this); + + // Discovery uses Firmata's standard REPORT_FIRMWARE query to distinguish a + // rover peripheral from unrelated Firmata devices. The helper owns this + // identity so every sketch gets it without repeating protocol boilerplate. + Firmata.setFirmwareNameAndVersion("RoverPeripheralFirmata", 1, 0); + + // SET_DIGITAL_PIN_VALUE is a fixed Firmata command rather than SysEx, so it + // cannot travel through FirmataFeature::handleSysex. Firmata exposes one + // callback for it and this peripheral owns the standard output implementation. + Firmata.attach(SET_DIGITAL_PIN_VALUE, digitalPinValueCallback); + Firmata.attach(SYSTEM_RESET, systemResetCallback); +} + +void RoverPeripheralFirmata::update() { + // Custom callbacks execute synchronously from Firmata's parser for now. This + // method intentionally remains available so future non-blocking peripheral + // work can be serviced without changing the sketch's main loop shape. +} + +void RoverPeripheralFirmata::handleCapability(byte pin) { + if (!IS_PIN_DIGITAL(pin)) { + return; + } + + // The peripheral supports the output modes roverd may select. Capability + // reporting stays standard Firmata, so the Linux probe can also inspect it + // with any other conforming client. + Firmata.write(PIN_MODE_OUTPUT); + Firmata.write(1); + if (IS_PIN_PWM(pin)) { + Firmata.write(PIN_MODE_PWM); + Firmata.write(DEFAULT_PWM_RESOLUTION); + } + Firmata.write(PIN_MODE_SERVO); + Firmata.write(14); +} + +boolean RoverPeripheralFirmata::handlePinMode(byte pin, int mode) { + if (pin >= TOTAL_PINS || !IS_PIN_DIGITAL(pin)) { + return false; + } + + // A pin can only have one active hardware generator. Detaching a previous + // servo before switching modes prevents it from continuing to pulse after a + // later digital or PWM configuration takes ownership of the pin. + if (mode != PIN_MODE_SERVO) { + detachServo(pin); + } + + switch (mode) { + case PIN_MODE_OUTPUT: + pinMode(PIN_TO_DIGITAL(pin), OUTPUT); + digitalWrite(PIN_TO_DIGITAL(pin), LOW); + Firmata.setPinState(pin, 0); + return true; + case PIN_MODE_PWM: + if (!IS_PIN_PWM(pin)) { + return false; + } + pinMode(PIN_TO_PWM(pin), OUTPUT); + analogWrite(PIN_TO_PWM(pin), 0); + Firmata.setPinState(pin, 0); + return true; + case PIN_MODE_SERVO: + attachServo(pin); + Firmata.setPinState(pin, 0); + return true; + default: + return false; + } +} + +boolean RoverPeripheralFirmata::handleSysex(byte command, byte argc, byte* argv) { + if (command == kPeripheralFeature) { + if (argc == 0) { + return true; + } + if (argv[0] == kDescribeOperation) { + buildAndSendDescription(); + } else if (argv[0] == kControlOperation) { + dispatchCustomControl(argc, argv); + } + return true; + } + + if (command == SERVO_CONFIG && argc >= 5) { + const byte pin = argv[0]; + const int minimumPulse = argv[1] | (argv[2] << 7); + const int maximumPulse = argv[3] | (argv[4] << 7); + if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) { + Firmata.setPinMode(pin, PIN_MODE_SERVO); + attachServo(pin, minimumPulse, maximumPulse); + } + return true; + } + + if (command == EXTENDED_ANALOG && argc >= 2) { + const byte pin = argv[0]; + if (pin >= TOTAL_PINS) { + return true; + } + + int value = 0; + // Extended analog values contain a variable number of seven-bit chunks. + // Reassembling every received chunk keeps servo angles and PWM values fully + // compatible with normal Firmata clients rather than assuming eight bits. + for (byte index = 1; index < argc && index <= 4; ++index) { + value |= static_cast(argv[index]) << (7 * (index - 1)); + } + + const byte mode = Firmata.getPinMode(pin); + if (mode == PIN_MODE_PWM && IS_PIN_PWM(pin)) { + analogWrite(PIN_TO_PWM(pin), value); + Firmata.setPinState(pin, value); + } else if (mode == PIN_MODE_SERVO && servos_[pin] != nullptr) { + servos_[pin]->write(value); + Firmata.setPinState(pin, value); + } + return true; + } + + return false; +} + +void RoverPeripheralFirmata::reset() { + for (byte pin = 0; pin < TOTAL_PINS; ++pin) { + detachServo(pin); + } + + // Built-in role defaults are applied on Firmata reset as well as boot. This + // makes reconnecting a client deterministic without creating a second state + // model on the ESP32. + if (hasHeadlight_) { + pinMode(headlight_.pin, OUTPUT); + const bool physicalHigh = headlight_.initiallyOn != (headlight_.polarity == OutputPolarity::ActiveLow); + writeDigitalPin(headlight_.pin, physicalHigh); + } + if (hasLaser_) { + pinMode(laser_.pin, OUTPUT); + const bool physicalHigh = laser_.initiallyOn != (laser_.polarity == OutputPolarity::ActiveLow); + writeDigitalPin(laser_.pin, physicalHigh); + } +} + +void RoverPeripheralFirmata::buildAndSendDescription() { + JsonDocument document; + document["name"] = name_; + + if (hasCameraServo_ || hasHeadlight_ || hasLaser_) { + JsonObject roverControls = document["roverControls"].to(); + if (hasCameraServo_) { + JsonObject servo = roverControls["cameraServo"].to(); + servo["pin"] = cameraServo_.pin; + servo["minimumAngleDegrees"] = cameraServo_.minimumAngleDegrees; + servo["maximumAngleDegrees"] = cameraServo_.maximumAngleDegrees; + servo["homeAngleDegrees"] = cameraServo_.homeAngleDegrees; + servo["nudgeDegrees"] = cameraServo_.nudgeDegrees; + servo["minimumPulseMicroseconds"] = cameraServo_.minimumPulseMicroseconds; + servo["maximumPulseMicroseconds"] = cameraServo_.maximumPulseMicroseconds; + servo["allowRawPulse"] = cameraServo_.allowRawPulse; + servo["inverted"] = cameraServo_.inverted; + } + + auto addDigitalRole = [&roverControls](const char* key, const RoverDigitalOutputConfig& config) { + JsonObject role = roverControls[key].to(); + role["pin"] = config.pin; + role["activeLow"] = config.polarity == OutputPolarity::ActiveLow; + role["initiallyOn"] = config.initiallyOn; + }; + if (hasHeadlight_) { + addDigitalRole("headlight", headlight_); + } + if (hasLaser_) { + addDigitalRole("laser", laser_); + } + } + + JsonArray controls = document["controls"].to(); + for (const ControlRegistration& registration : controls_) { + JsonObject control = controls.add(); + control["id"] = registration.id; + control["name"] = registration.name; + + switch (registration.type) { + case ControlType::Slider: + control["type"] = "slider"; + control["min"] = registration.minimum; + control["max"] = registration.maximum; + break; + case ControlType::Button: + control["type"] = "button"; + control["mode"] = buttonModeName(registration.buttonMode); + break; + case ControlType::Number: + control["type"] = "number"; + control["min"] = registration.minimum; + control["max"] = registration.maximum; + break; + case ControlType::Text: + control["type"] = "text"; + control["maxLength"] = registration.maximumLength; + break; + } + + JsonObject output = control["output"].to(); + output["type"] = outputTypeName(static_cast(registration.output)); + if (registration.output != OutputType::Custom) { + output["pin"] = registration.pin; + } + if (registration.output == OutputType::Digital && registration.polarity == OutputPolarity::ActiveLow) { + output["activeLow"] = true; + } + } + + String payload; + serializeJson(document, payload); + + // ConfigurableFirmata's convenience sendSysex takes a byte-sized raw length. + // Descriptions can exceed that, so write the standard framing and each 7-bit + // pair directly. This remains one ordinary Firmata SysEx message on the wire. + Firmata.startSysex(); + Firmata.write(kPeripheralFeature); + Firmata.write(kDescriptionOperation); + for (size_t index = 0; index < payload.length(); ++index) { + Firmata.sendValueAsTwo7bitBytes(static_cast(payload[index])); + } + Firmata.endSysex(); +} + +void RoverPeripheralFirmata::dispatchCustomControl(byte argc, byte* argv) { + if (argc < 3 || ((argc - 1) % 2) != 0) { + Firmata.sendString(F("Invalid rover control payload")); + return; + } + + String decoded; + decoded.reserve((argc - 1) / 2); + for (byte index = 1; index + 1 < argc; index += 2) { + if (argv[index + 1] > 1) { + Firmata.sendString(F("Invalid rover control encoding")); + return; + } + decoded += static_cast(argv[index] | (argv[index + 1] << 7)); + } + + JsonDocument document; + if (deserializeJson(document, decoded) != DeserializationError::Ok) { + Firmata.sendString(F("Invalid rover control JSON")); + return; + } + + const String controlID = document["control"].as(); + for (ControlRegistration& registration : controls_) { + if (registration.id != controlID || registration.output != OutputType::Custom) { + continue; + } + + // The registration type is the source of truth for value conversion. This + // prevents an unexpected JSON value from silently selecting a different + // callback signature or invoking unrelated application behavior. + switch (registration.type) { + case ControlType::Slider: + if (registration.sliderCallback) { + registration.sliderCallback(document["value"].as()); + } + break; + case ControlType::Button: + if (registration.buttonCallback) { + registration.buttonCallback(document["value"].as()); + } + break; + case ControlType::Number: + if (registration.numberCallback) { + registration.numberCallback(document["value"].as()); + } + break; + case ControlType::Text: + if (registration.textCallback) { + String value = document["value"].as(); + if (value.length() > registration.maximumLength) { + value.remove(registration.maximumLength); + } + registration.textCallback(value); + } + break; + } + return; + } + + Firmata.sendString(F("Unknown rover control")); +} + +void RoverPeripheralFirmata::writeDigitalPin(byte pin, bool physicalHigh) { + // Standard Firmata digital values represent the electrical pin level. roverd + // applies the advertised activeLow mapping before sending a command, keeping + // this firmware compatible with raw Firmata clients and avoiding inversion in + // two different layers. + digitalWrite(PIN_TO_DIGITAL(pin), physicalHigh ? HIGH : LOW); + Firmata.setPinState(pin, physicalHigh ? 1 : 0); +} + +void RoverPeripheralFirmata::attachServo(byte pin, int minimumPulseMicroseconds, int maximumPulseMicroseconds) { + if (pin >= TOTAL_PINS || !IS_PIN_DIGITAL(pin)) { + return; + } + if (servos_[pin] == nullptr) { + servos_[pin] = new Servo(); + } + if (servos_[pin]->attached()) { + servos_[pin]->detach(); + } + if (minimumPulseMicroseconds > 0 && maximumPulseMicroseconds > minimumPulseMicroseconds) { + servos_[pin]->attach(PIN_TO_SERVO(pin), minimumPulseMicroseconds, maximumPulseMicroseconds); + } else { + servos_[pin]->attach(PIN_TO_SERVO(pin)); + } +} + +void RoverPeripheralFirmata::detachServo(byte pin) { + if (pin >= TOTAL_PINS || servos_[pin] == nullptr) { + return; + } + if (servos_[pin]->attached()) { + servos_[pin]->detach(); + } + delete servos_[pin]; + servos_[pin] = nullptr; +} + +void RoverPeripheralFirmata::digitalPinValueCallback(byte pin, int value) { + if (instance_ == nullptr || pin >= TOTAL_PINS || Firmata.getPinMode(pin) != PIN_MODE_OUTPUT) { + return; + } + + // Polarity is advertised by the peripheral and applied by roverd before this + // standard raw pin-level command reaches the ESP32. + instance_->writeDigitalPin(pin, value != 0); +} + +void RoverPeripheralFirmata::systemResetCallback() { + if (instance_ != nullptr) { + instance_->reset(); + } +} diff --git a/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.h b/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.h new file mode 100644 index 00000000..44027a4a --- /dev/null +++ b/esp32/libraries/RoverPeripheralFirmata/src/internal/RoverPeripheralFirmata.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +/* + * RoverPeripheralFirmata is the protocol-facing implementation behind the + * small RoverPeripheral public facade. Keeping this class private prevents + * peripheral sketches from depending on Firmata types while ordinary Firmata + * tooling can still use digital, PWM, and servo commands on the same stream. + */ +class RoverPeripheralFirmata : public FirmataFeature { + public: + explicit RoverPeripheralFirmata(const String& name); + + void setName(const String& name); + + void addServoSlider(const SliderControlConfig& config, const ServoOutput& output); + void addPwmSlider(const SliderControlConfig& config, const PwmOutput& output); + void addDigitalButton(const ButtonControlConfig& config, const DigitalOutput& output); + void addSlider(const SliderControlConfig& config, SliderCallback callback); + void addButton(const ButtonControlConfig& config, ButtonCallback callback); + void addNumber(const NumberControlConfig& config, NumberCallback callback); + void addText(const TextControlConfig& config, TextCallback callback); + + void addRoverCameraServo(const RoverCameraServoConfig& config); + void addRoverHeadlight(const RoverDigitalOutputConfig& config); + void addRoverLaser(const RoverDigitalOutputConfig& config); + + void begin(FirmataExt& extension); + void update(); + + // FirmataFeature methods let FirmataExt route standard and custom SysEx + // operations through the same parser that owns the serial connection. + void handleCapability(byte pin) override; + boolean handlePinMode(byte pin, int mode) override; + boolean handleSysex(byte command, byte argc, byte* argv) override; + void reset() override; + + private: + enum class ControlType { + Slider, + Button, + Number, + Text, + }; + + enum class OutputType { + Servo, + Pwm, + Digital, + Custom, + }; + + struct ControlRegistration { + String id; + String name; + ControlType type; + OutputType output; + int minimum = 0; + int maximum = 0; + size_t maximumLength = 0; + ButtonMode buttonMode = ButtonMode::Momentary; + uint8_t pin = 0; + OutputPolarity polarity = OutputPolarity::ActiveHigh; + SliderCallback sliderCallback; + ButtonCallback buttonCallback; + NumberCallback numberCallback; + TextCallback textCallback; + }; + + String name_; + std::vector controls_; + bool hasCameraServo_ = false; + bool hasHeadlight_ = false; + bool hasLaser_ = false; + RoverCameraServoConfig cameraServo_; + RoverDigitalOutputConfig headlight_; + RoverDigitalOutputConfig laser_; + Servo* servos_[TOTAL_PINS] = {}; + + void validateControlName(const String& name) const; + void validateRange(const String& name, int minimum, int maximum) const; + void buildAndSendDescription(); + void dispatchCustomControl(byte argc, byte* argv); + void writeDigitalPin(byte pin, bool enabled); + void attachServo(byte pin, int minimumPulseMicroseconds = -1, int maximumPulseMicroseconds = -1); + void detachServo(byte pin); + + static RoverPeripheralFirmata* instance_; + static void digitalPinValueCallback(byte pin, int value); + static void systemResetCallback(); +}; diff --git a/esp32/rover-gpio-peripheral/platformio.ini b/esp32/rover-gpio-peripheral/platformio.ini new file mode 100644 index 00000000..4cf8b8dc --- /dev/null +++ b/esp32/rover-gpio-peripheral/platformio.ini @@ -0,0 +1,25 @@ +[platformio] +default_envs = esp32dev + +[env] +platform = espressif32 +framework = arduino +monitor_speed = 115200 +lib_deps = + ; Install the local package through PlatformIO's dependency manager so this + ; reference project exercises the same transitive dependency behavior as an + ; external project using the published Registry package. + RoverPeripheral=file://../libraries/RoverPeripheralFirmata + +; This is the generic ESP32-WROOM-32/DevKitC target used by boards carrying a +; CH340 or CP210x USB-to-UART bridge. Linux normally exposes it as ttyUSB*. +[env:esp32dev] +board = esp32dev + +; Native USB boards use the same sketch and Firmata stream. These flags make the +; ESP32-S3's USB CDC serial port active at boot, normally appearing as ttyACM*. +[env:esp32-s3-devkitc-1] +board = esp32-s3-devkitc-1 +build_flags = + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 diff --git a/esp32/rover-gpio-peripheral/src/main.cpp b/esp32/rover-gpio-peripheral/src/main.cpp new file mode 100644 index 00000000..c19ee6c2 --- /dev/null +++ b/esp32/rover-gpio-peripheral/src/main.cpp @@ -0,0 +1,97 @@ +#include + +namespace { +// Every example pin is present on both the classic ESP32 DevKitC and the +// ESP32-S3 DevKitC. GPIO 19 and 20 are deliberately avoided because native-USB +// S3 boards use them for USB D- and D+. +constexpr uint8_t kSpecialActionPin = 21; + +int repeatCount = 1; +String displayMessage; + +void runSpecialAction(bool pressed) { + // Receiving both button edges lets application hardware remain active only + // while the driver holds the momentary control. + digitalWrite(kSpecialActionPin, pressed ? HIGH : LOW); +} + +void setRepeatCount(int value) { + // A real device can use this value when it starts its next animation or + // actuator sequence. Storing it keeps this reference callback non-blocking. + repeatCount = value; +} + +void setDisplayMessage(const String& value) { + // Display hardware can render the stored value from updateRoverPeripheral(). + // Avoiding Serial output is important because Serial belongs to Firmata. + displayMessage = value; +} +} // namespace + +void configureRoverPeripheral(RoverPeripheral& io) { + io.name("Rover GPIO"); + + // Standard roles retain the rover's existing HUD controls while moving the + // electrical outputs to this ESP32 on either a Pi or laptop rover host. + RoverCameraServoConfig cameraServo; + cameraServo.pin = 14; + cameraServo.minimumAngleDegrees = -15; + cameraServo.maximumAngleDegrees = 30; + cameraServo.homeAngleDegrees = 0; + cameraServo.nudgeDegrees = 2; + cameraServo.minimumPulseMicroseconds = 900; + cameraServo.maximumPulseMicroseconds = 2100; + cameraServo.allowRawPulse = false; + cameraServo.inverted = false; + io.addCameraServo(cameraServo); + + RoverDigitalOutputConfig headlight; + headlight.pin = 18; + headlight.polarity = OutputPolarity::ActiveHigh; + headlight.initiallyOn = false; + io.addHeadlight(headlight); + + RoverDigitalOutputConfig laser; + laser.pin = 16; + laser.polarity = OutputPolarity::ActiveHigh; + laser.initiallyOn = false; + io.addLaser(laser); + + pinMode(kSpecialActionPin, OUTPUT); + digitalWrite(kSpecialActionPin, LOW); + + // Accessory controls render in precisely this registration order. + SliderControlConfig servoPosition; + servoPosition.name = "Servo position"; + servoPosition.minimum = 0; + servoPosition.maximum = 180; + + ServoOutput servoOutput; + servoOutput.pin = 13; + io.addSlider(servoPosition, servoOutput); + + SliderControlConfig lightBrightness; + lightBrightness.name = "Light brightness"; + lightBrightness.minimum = 0; + lightBrightness.maximum = 255; + + PwmOutput lightOutput; + lightOutput.pin = 17; + io.addSlider(lightBrightness, lightOutput); + + ButtonControlConfig specialAction; + specialAction.name = "Special action"; + specialAction.mode = ButtonMode::Momentary; + io.addButton(specialAction, runSpecialAction); + + NumberControlConfig repeats; + repeats.name = "Repeat count"; + repeats.minimum = 1; + repeats.maximum = 20; + io.addNumber(repeats, setRepeatCount); + + TextControlConfig message; + message.name = "Display message"; + message.maximumLength = 64; + io.addText(message, setDisplayMessage); +} diff --git a/pi/roverd/camera_servo.go b/pi/roverd/camera_servo.go index 83382ca5..50a1216b 100644 --- a/pi/roverd/camera_servo.go +++ b/pi/roverd/camera_servo.go @@ -25,10 +25,6 @@ type CameraServo struct { closed bool } -const maxServoDegPerSec = 60.0 -const servoStepInterval = 20 * time.Millisecond -const servoAngleEpsilon = 0.01 - func NewCameraServo(cfg CameraServoConfig, logger *log.Logger) (*CameraServo, error) { if !cfg.Enabled { return nil, fmt.Errorf("camera servo disabled") @@ -132,6 +128,16 @@ func (s *CameraServo) CurrentAngle() float64 { return s.currentAngle } +// Configuration reports the effective public behavior advertised to the +// server. The native implementation simply returns its validated YAML config. +func (s *CameraServo) Configuration() CameraServoConfig { + return s.cfg +} + +func (s *CameraServo) BackendDescription() string { + return "native GPIO" +} + func (s *CameraServo) applyPulseLocked(micros int) { micros = clampInt(micros, s.cfg.MinPulseUs, s.cfg.MaxPulseUs) s.pin.DutyCycle(uint32(micros), uint32(s.cfg.CycleLen)) diff --git a/pi/roverd/camera_servo_debian_laptop.go b/pi/roverd/camera_servo_debian_laptop.go index 16dc3f5f..8f9ec186 100644 --- a/pi/roverd/camera_servo_debian_laptop.go +++ b/pi/roverd/camera_servo_debian_laptop.go @@ -11,10 +11,9 @@ type CameraServo struct{} func NewCameraServo(_ CameraServoConfig, _ *log.Logger) (*CameraServo, error) { /* - The Debian laptop profile starts with the laptop's built-in webcam and no - Pi PWM servo. If a laptop rover eventually grows an external servo board, - it should get its own implementation instead of reusing Raspberry Pi GPIO - assumptions. + This constructor represents only native host GPIO. The shared startup + resolver selects the normal Firmata implementation when an ESP32 provides + the role, so external hardware is not laptop-specific code. */ return nil, fmt.Errorf("camera servo not supported in the debian-laptop build") } @@ -36,3 +35,11 @@ func (c *CameraServo) SetPulseWidth(micros int) error { func (c *CameraServo) CurrentAngle() float64 { return 0 } + +func (c *CameraServo) Configuration() CameraServoConfig { + return CameraServoConfig{} +} + +func (c *CameraServo) BackendDescription() string { + return "native GPIO" +} diff --git a/pi/roverd/camera_servo_dummy.go b/pi/roverd/camera_servo_dummy.go index 5e407ae6..04ff5384 100644 --- a/pi/roverd/camera_servo_dummy.go +++ b/pi/roverd/camera_servo_dummy.go @@ -30,3 +30,11 @@ func (c *CameraServo) SetPulseWidth(micros int) error { func (c *CameraServo) CurrentAngle() float64 { return 0 } + +func (c *CameraServo) Configuration() CameraServoConfig { + return CameraServoConfig{} +} + +func (c *CameraServo) BackendDescription() string { + return "native GPIO" +} diff --git a/pi/roverd/cmd/peripheral-probe/main.go b/pi/roverd/cmd/peripheral-probe/main.go new file mode 100644 index 00000000..388aadee --- /dev/null +++ b/pi/roverd/cmd/peripheral-probe/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + "time" + + roverd "multiroombarover/pi/roverd" + + "github.com/tarm/serial" +) + +func main() { + var portName string + var baud int + var timeout time.Duration + var startupWait time.Duration + var controlID string + var rawValue string + + flag.StringVar(&portName, "port", "", "serial device, for example /dev/ttyUSB0 or /dev/ttyACM0") + flag.IntVar(&baud, "baud", 115200, "Firmata serial baud rate") + flag.DurationVar(&timeout, "timeout", 5*time.Second, "timeout for each Firmata response") + flag.DurationVar(&startupWait, "startup-wait", 2*time.Second, "time allowed for boards that reset when the port opens") + flag.StringVar(&controlID, "control", "", "optional declared control ID to exercise") + flag.StringVar(&rawValue, "value", "", "JSON value for -control, such as 90, true, or \"hello\"") + flag.Parse() + + if portName == "" { + log.Fatal("-port is required") + } + if (controlID == "") != (rawValue == "") { + log.Fatal("-control and -value must be provided together") + } + + port, err := serial.OpenPort(&serial.Config{ + Name: portName, + Baud: baud, + ReadTimeout: 100 * time.Millisecond, + }) + if err != nil { + log.Fatalf("open %s: %v", portName, err) + } + defer port.Close() + + // CH340 and native-USB development boards may reset when the host opens the + // port. Waiting here makes the same probe work with both connection styles + // without baking that diagnostic delay into the production Firmata client. + time.Sleep(startupWait) + + rootContext, cancelRoot := context.WithCancel(context.Background()) + defer cancelRoot() + client := roverd.NewFirmataClient(port) + client.Start(rootContext) + + firmware, err := withTimeout(timeout, client.QueryFirmware) + if err != nil { + log.Fatalf("query firmware: %v", err) + } + fmt.Printf("Firmata firmware: %s %d.%d\n", firmware.Name, firmware.Major, firmware.Minor) + + capabilities, err := withTimeout(timeout, client.QueryCapabilities) + if err != nil { + log.Fatalf("query capabilities: %v", err) + } + fmt.Printf("Firmata pins described: %d\n", len(capabilities)) + + description, err := withTimeout(timeout, client.Describe) + if err != nil { + log.Fatalf("describe rover peripheral: %v", err) + } + formatted, err := json.MarshalIndent(description, "", " ") + if err != nil { + log.Fatalf("format description: %v", err) + } + fmt.Printf("Peripheral description:\n%s\n", formatted) + + if controlID != "" { + if err := exerciseControl(client, description, controlID, json.RawMessage(rawValue)); err != nil { + log.Fatalf("exercise control %q: %v", controlID, err) + } + fmt.Fprintf(os.Stdout, "Control %q accepted.\n", controlID) + } +} + +// withTimeout gives every boot-time exchange its own deadline. A missing board +// therefore reports the exact handshake stage that failed instead of consuming +// one shared timeout and obscuring which response was absent. +func withTimeout[T any](timeout time.Duration, operation func(context.Context) (T, error)) (T, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return operation(ctx) +} + +func exerciseControl(client *roverd.FirmataClient, description roverd.PeripheralDescription, controlID string, rawValue json.RawMessage) error { + var selected *roverd.PeripheralControl + for index := range description.Controls { + if description.Controls[index].ID == controlID { + selected = &description.Controls[index] + break + } + } + if selected == nil { + return errors.New("control is not present in the device description") + } + + var value any + if err := json.Unmarshal(rawValue, &value); err != nil { + return fmt.Errorf("parse -value as JSON: %w", err) + } + + // Standard outputs deliberately use standard Firmata commands. Only custom + // callbacks use the rover-peripheral CONTROL operation, which is the central + // distinction the probe is intended to validate on real hardware. + switch selected.Output.Type { + case "custom": + return client.SendPeripheralControl(selected.ID, value) + case "digital": + enabled, ok := value.(bool) + if !ok { + return errors.New("digital control value must be true or false") + } + if selected.Output.ActiveLow { + enabled = !enabled + } + if err := client.SetPinMode(byte(*selected.Output.Pin), roverd.FirmataPinModeOutput); err != nil { + return err + } + return client.SetDigitalPin(byte(*selected.Output.Pin), enabled) + case "pwm", "servo": + number, ok := value.(float64) + if !ok || number != float64(int(number)) { + return errors.New("PWM and servo control values must be whole numbers") + } + mode := roverd.FirmataPinModePWM + if selected.Output.Type == "servo" { + mode = roverd.FirmataPinModeServo + } + if err := client.SetPinMode(byte(*selected.Output.Pin), mode); err != nil { + return err + } + return client.ExtendedAnalog(byte(*selected.Output.Pin), int(number)) + default: + return fmt.Errorf("unsupported output %q", selected.Output.Type) + } +} diff --git a/pi/roverd/cmd/roverd/main.go b/pi/roverd/cmd/roverd/main.go index 7911d163..89fd97b7 100644 --- a/pi/roverd/cmd/roverd/main.go +++ b/pi/roverd/cmd/roverd/main.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + "fmt" "log" "os" "os/signal" @@ -37,6 +38,19 @@ func main() { } defer serialPort.Close() + // Peripheral discovery is intentionally a boot-time operation. The manager + // keeps successful USB ports open across server WebSocket reconnects and is + // rebuilt only when the roverd process itself restarts. + peripherals, err := roverd.DiscoverPeripheralManager(ctx, cfg.Serial.Device, logger) + if err != nil { + console.Notify(fmt.Sprintf("Rover peripheral startup failed: %v", err)) + logger.Fatalf("discover rover peripherals: %v", err) + } + defer peripherals.Close() + for _, message := range peripherals.StartupBroadcasts() { + console.Notify(message) + } + var pulser *roverd.BRCPulser if cfg.BRC.Enabled() { pulser, err = roverd.NewBRCPulser(cfg.BRC, logger) @@ -61,37 +75,41 @@ func main() { mediaSupervisor.Start(ctx) } - var cameraServo *roverd.CameraServo - if cfg.CameraServo.Enabled { - cameraServo, err = roverd.NewCameraServo(cfg.CameraServo, logger) - if err != nil { - logger.Fatalf("init camera servo: %v", err) - } - defer cameraServo.Close() + // Backend selection is identical on Pi and laptop hosts: enabled native + // GPIO wins, otherwise a discovered ESP32 may provide the built-in role. + hardwareControllers, err := roverd.ResolveRoverHardwareControllers(cfg, peripherals, logger) + if err != nil { + console.Notify(fmt.Sprintf("Rover peripheral startup failed while selecting hardware: %v", err)) + logger.Fatalf("resolve rover hardware controllers: %v", err) + } + defer hardwareControllers.Close() + for _, message := range hardwareControllers.StartupBroadcasts() { + console.Notify(message) } - var headlight *roverd.GPIOToggle - if cfg.Headlight.Enabled { - headlight, err = roverd.NewGPIOToggle("headlight", cfg.Headlight, logger) - if err != nil { - logger.Fatalf("init headlight: %v", err) + // A peripheral is never hot-reconnected. Report the first terminal serial + // failure for each discovered board and tell the local operator exactly what + // recovery action the fixed boot-time lifecycle requires. + go func() { + for { + select { + case failure := <-peripherals.Failures(): + console.Notify(fmt.Sprintf( + "Rover peripheral %q (%s) disconnected: %v. Reconnect it and restart roverd.", + failure.Name, + failure.ID, + failure.Err, + )) + case <-ctx.Done(): + return + } } - defer headlight.Close() - } - - var laser *roverd.GPIOToggle - if cfg.Laser.Enabled { - laser, err = roverd.NewGPIOToggle("laser", cfg.Laser, logger) - if err != nil { - logger.Fatalf("init laser: %v", err) - } - defer laser.Close() - } + }() autoCharge := roverd.NewAutoChargeController(adapter, eventStream, logger) go autoCharge.Run(ctx, sensorSamples) - client := roverd.NewWSClient(cfg, adapter, sensorFrames, eventStream, mediaSupervisor, cameraServo, headlight, laser, logger, console) + client := roverd.NewWSClient(cfg, adapter, sensorFrames, eventStream, mediaSupervisor, hardwareControllers.CameraServo, hardwareControllers.Headlight, hardwareControllers.Laser, peripherals, logger, console) // Startup is announced only after every configured hardware dependency has // initialized successfully. A message here therefore means the control loop diff --git a/pi/roverd/command.go b/pi/roverd/command.go index e9e2e2ca..9621e11e 100644 --- a/pi/roverd/command.go +++ b/pi/roverd/command.go @@ -1,19 +1,22 @@ package roverd +import "encoding/json" + type helloMessage struct { - Type string `json:"type"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Color string `json:"color,omitempty"` - Battery BatteryConfig `json:"battery"` - MaxWheelSpeed int `json:"maxWheelSpeed"` - Media MediaConfig `json:"media"` - CameraServo CameraServoConfig `json:"cameraServo"` - Audio AudioConfig `json:"audio"` - Horn HornConfig `json:"horn"` - Headlight GPIOToggleConfig `json:"headlight"` - Laser GPIOToggleConfig `json:"laser"` - Private PrivateConfig `json:"private"` + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Color string `json:"color,omitempty"` + Battery BatteryConfig `json:"battery"` + MaxWheelSpeed int `json:"maxWheelSpeed"` + Media MediaConfig `json:"media"` + CameraServo CameraServoConfig `json:"cameraServo"` + Audio AudioConfig `json:"audio"` + Horn HornConfig `json:"horn"` + Headlight GPIOToggleConfig `json:"headlight"` + Laser GPIOToggleConfig `json:"laser"` + Peripherals []RoverPeripheralMetadata `json:"peripherals,omitempty"` + Private PrivateConfig `json:"private"` } type sensorMessage struct { @@ -45,6 +48,7 @@ type inboundMessage struct { AudioLevels *audioLevelsPayload `json:"audioLevels,omitempty"` Headlight *togglePayload `json:"headlight,omitempty"` Laser *togglePayload `json:"laser,omitempty"` + Peripheral *peripheralPayload `json:"peripheral,omitempty"` Song *songPayload `json:"song,omitempty"` Reboot *rebootPayload `json:"reboot,omitempty"` // Update is intentionally just a marker payload. The server can request the @@ -103,6 +107,12 @@ type togglePayload struct { Action string `json:"action"` } +type peripheralPayload struct { + ID string `json:"id"` + Control string `json:"control"` + Value json.RawMessage `json:"value"` +} + type songPayload struct { Slot *int `json:"slot,omitempty"` Notes []songNote `json:"notes"` diff --git a/pi/roverd/firmata.go b/pi/roverd/firmata.go new file mode 100644 index 00000000..027dafaa --- /dev/null +++ b/pi/roverd/firmata.go @@ -0,0 +1,664 @@ +package roverd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "sync" +) + +// Firmata command and mode constants are kept here instead of scattering raw +// bytes through the peripheral code. The values come directly from the Firmata +// protocol, so captures from a rover can be compared with the specification. +const ( + firmataReportVersion byte = 0xF9 + firmataSetPinMode byte = 0xF4 + firmataSetDigitalPin byte = 0xF5 + firmataStartSysex byte = 0xF0 + firmataEndSysex byte = 0xF7 + firmataReportFirmware byte = 0x79 + firmataCapabilityQuery byte = 0x6B + firmataCapabilityReply byte = 0x6C + firmataExtendedAnalog byte = 0x6F + firmataServoConfig byte = 0x70 + firmataPeripheralFeature byte = 0x01 + + firmataPeripheralDescribe byte = 0x00 + firmataPeripheralDescription byte = 0x01 + firmataPeripheralControl byte = 0x02 + firmataMaximumSysexDataBytes = 252 + + FirmataPinModeOutput byte = 0x01 + FirmataPinModePWM byte = 0x03 + FirmataPinModeServo byte = 0x04 +) + +// FirmataMessage is the transport-neutral result of parsing one complete +// Firmata message. For SysEx messages Command is the SysEx feature byte and +// Data is everything between that feature byte and END_SYSEX. +type FirmataMessage struct { + Command byte + Data []byte + Sysex bool +} + +// FirmataParser incrementally parses a byte stream. USB serial reads may split +// a message anywhere or combine several messages, so parsing whole Read calls +// as though they were packets would intermittently corrupt valid traffic. +type FirmataParser struct { + inSysex bool + sysex []byte + command byte + data []byte + expected int +} + +// Feed accepts any fragment of the serial stream and returns every complete +// message found in it, preserving wire order. +func (p *FirmataParser) Feed(fragment []byte) ([]FirmataMessage, error) { + var messages []FirmataMessage + + for _, value := range fragment { + if p.inSysex { + switch { + case value == firmataEndSysex: + if len(p.sysex) == 0 { + p.resetSysex() + return messages, errors.New("Firmata SysEx message is missing a feature byte") + } + messages = append(messages, FirmataMessage{ + Command: p.sysex[0], + Data: append([]byte(nil), p.sysex[1:]...), + Sysex: true, + }) + p.resetSysex() + case value&0x80 != 0: + // Bytes inside SysEx must be seven-bit clean. Reset immediately so + // a damaged frame cannot consume every later message on the port. + p.resetSysex() + return messages, fmt.Errorf("invalid 8-bit value 0x%02x inside Firmata SysEx", value) + default: + p.sysex = append(p.sysex, value) + } + continue + } + + if value == firmataStartSysex { + p.inSysex = true + p.sysex = p.sysex[:0] + p.resetFixed() + continue + } + + if value&0x80 != 0 { + p.command = value + p.data = p.data[:0] + p.expected = firmataDataLength(value) + if p.expected == 0 { + messages = append(messages, FirmataMessage{Command: value}) + p.resetFixed() + } + continue + } + + // Stray data before a status byte is harmless serial noise. Firmata + // has no framing information that could assign it to a command. + if p.expected == 0 { + continue + } + p.data = append(p.data, value) + if len(p.data) == p.expected { + messages = append(messages, FirmataMessage{ + Command: p.command, + Data: append([]byte(nil), p.data...), + }) + p.resetFixed() + } + } + + return messages, nil +} + +func (p *FirmataParser) resetSysex() { + p.inSysex = false + p.sysex = p.sysex[:0] +} + +func (p *FirmataParser) resetFixed() { + p.command = 0 + p.data = p.data[:0] + p.expected = 0 +} + +// firmataDataLength returns the number of seven-bit data bytes used by the +// fixed-length messages relevant to normal Firmata traffic. Unknown system +// commands are treated as single-byte messages so they cannot stall parsing of +// the rover-peripheral SysEx frames that follow them. +func firmataDataLength(command byte) int { + switch command { + case firmataReportVersion, firmataSetPinMode, firmataSetDigitalPin: + return 2 + } + + switch command & 0xF0 { + case 0x80, 0x90, 0xA0, 0xE0: + return 2 + case 0xC0, 0xD0: + return 1 + default: + return 0 + } +} + +// EncodeFirmata7Bit converts arbitrary bytes into the two-byte representation +// required inside Firmata SysEx. Keeping this transform below the JSON layer +// means firmware authors and UI code never need to think about wire encoding. +func EncodeFirmata7Bit(raw []byte) []byte { + encoded := make([]byte, 0, len(raw)*2) + for _, value := range raw { + encoded = append(encoded, value&0x7F, (value>>7)&0x01) + } + return encoded +} + +// DecodeFirmata7Bit reverses EncodeFirmata7Bit and rejects malformed pairs. +func DecodeFirmata7Bit(encoded []byte) ([]byte, error) { + if len(encoded)%2 != 0 { + return nil, fmt.Errorf("Firmata 7-bit payload has odd length %d", len(encoded)) + } + + decoded := make([]byte, 0, len(encoded)/2) + for index := 0; index < len(encoded); index += 2 { + low, high := encoded[index], encoded[index+1] + if low&0x80 != 0 || high > 1 { + return nil, fmt.Errorf("invalid Firmata 7-bit pair at byte %d", index) + } + decoded = append(decoded, low|(high<<7)) + } + return decoded, nil +} + +// PeripheralDescription is generated by the ESP32 at boot. Controls is a slice +// intentionally: registration order is part of the UI contract and must never +// be replaced by map iteration or alphabetical sorting. +type PeripheralDescription struct { + Name string `json:"name"` + RoverControls PeripheralRoverControls `json:"roverControls,omitempty"` + Controls []PeripheralControl `json:"controls"` +} + +type PeripheralRoverControls struct { + CameraServo *PeripheralCameraServo `json:"cameraServo,omitempty"` + Headlight *PeripheralDigitalRole `json:"headlight,omitempty"` + Laser *PeripheralDigitalRole `json:"laser,omitempty"` +} + +type PeripheralCameraServo struct { + Pin int `json:"pin"` + MinimumAngleDegrees float64 `json:"minimumAngleDegrees"` + MaximumAngleDegrees float64 `json:"maximumAngleDegrees"` + HomeAngleDegrees float64 `json:"homeAngleDegrees"` + NudgeDegrees float64 `json:"nudgeDegrees"` + MinimumPulseMicroseconds int `json:"minimumPulseMicroseconds"` + MaximumPulseMicroseconds int `json:"maximumPulseMicroseconds"` + AllowRawPulse bool `json:"allowRawPulse"` + Inverted bool `json:"inverted"` +} + +type PeripheralDigitalRole struct { + Pin int `json:"pin"` + ActiveLow bool `json:"activeLow"` + InitiallyOn bool `json:"initiallyOn"` +} + +type PeripheralControl struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Mode string `json:"mode,omitempty"` + Minimum *int `json:"min,omitempty"` + Maximum *int `json:"max,omitempty"` + MaximumLength *int `json:"maxLength,omitempty"` + Output PeripheralOutput `json:"output"` +} + +type PeripheralOutput struct { + Type string `json:"type"` + Pin *int `json:"pin,omitempty"` + ActiveLow bool `json:"activeLow,omitempty"` +} + +// Validate catches authoring mistakes at connection time, where the error can +// name the offending peripheral, instead of allowing a malformed declaration +// to turn into a confusing no-op later when a driver uses the control. +func (description PeripheralDescription) Validate() error { + if description.Name == "" { + return errors.New("peripheral description requires a name") + } + if camera := description.RoverControls.CameraServo; camera != nil { + if err := validateFirmataPin("cameraServo", camera.Pin); err != nil { + return err + } + if camera.MinimumAngleDegrees >= camera.MaximumAngleDegrees { + return errors.New("cameraServo angle range must be increasing") + } + if camera.HomeAngleDegrees < camera.MinimumAngleDegrees || camera.HomeAngleDegrees > camera.MaximumAngleDegrees { + return errors.New("cameraServo home angle must be inside its angle range") + } + if camera.NudgeDegrees <= 0 { + return errors.New("cameraServo nudge must be positive") + } + if camera.MinimumPulseMicroseconds <= 0 || camera.MaximumPulseMicroseconds <= camera.MinimumPulseMicroseconds { + return errors.New("cameraServo pulse range must be positive and increasing") + } + } + if role := description.RoverControls.Headlight; role != nil { + if err := validateFirmataPin("headlight", role.Pin); err != nil { + return err + } + } + if role := description.RoverControls.Laser; role != nil { + if err := validateFirmataPin("laser", role.Pin); err != nil { + return err + } + } + + seen := make(map[string]struct{}, len(description.Controls)) + for index, control := range description.Controls { + if control.ID == "" || control.Name == "" { + return fmt.Errorf("control %d requires both id and name", index) + } + if _, exists := seen[control.ID]; exists { + return fmt.Errorf("control id %q is duplicated", control.ID) + } + seen[control.ID] = struct{}{} + + switch control.Type { + case "slider", "number": + if control.Minimum == nil || control.Maximum == nil || *control.Minimum > *control.Maximum { + return fmt.Errorf("control %q requires a valid min and max", control.ID) + } + case "button": + if control.Mode != "toggle" && control.Mode != "momentary" { + return fmt.Errorf("button %q requires toggle or momentary mode", control.ID) + } + case "text": + if control.MaximumLength == nil || *control.MaximumLength <= 0 { + return fmt.Errorf("text control %q requires a positive maxLength", control.ID) + } + default: + return fmt.Errorf("control %q has unsupported type %q", control.ID, control.Type) + } + + switch control.Output.Type { + case "digital": + if control.Output.Pin == nil { + return fmt.Errorf("control %q output %q requires a pin", control.ID, control.Output.Type) + } + if err := validateFirmataPin("control "+control.ID, *control.Output.Pin); err != nil { + return err + } + if control.Type != "button" { + return fmt.Errorf("digital output control %q must be a button", control.ID) + } + case "pwm", "servo": + if control.Output.Pin == nil { + return fmt.Errorf("control %q output %q requires a pin", control.ID, control.Output.Type) + } + if err := validateFirmataPin("control "+control.ID, *control.Output.Pin); err != nil { + return err + } + if control.Type != "slider" && control.Type != "number" { + return fmt.Errorf("%s output control %q must be a slider or number", control.Output.Type, control.ID) + } + case "custom": + default: + return fmt.Errorf("control %q has unsupported output %q", control.ID, control.Output.Type) + } + } + + return nil +} + +func validateFirmataPin(owner string, pin int) error { + // Firmata represents pin numbers with one seven-bit byte. Rejecting values + // outside that wire range avoids silently wrapping a declaration when it is + // converted to a byte for output commands. + if pin < 0 || pin > 127 { + return fmt.Errorf("%s pin must be between 0 and 127", owner) + } + return nil +} + +// FirmataFirmware identifies the implementation answering the standard +// REPORT_FIRMWARE query. It is diagnostic metadata, not a protocol gate. +type FirmataFirmware struct { + Major int + Minor int + Name string +} + +// FirmataPinCapability is one mode/resolution pair from CAPABILITY_RESPONSE. +type FirmataPinCapability struct { + Mode byte + Resolution byte +} + +// FirmataClient owns one already-open serial connection. Its reader goroutine +// separates arbitrary USB read boundaries from request/response handling while +// writeMu prevents two commands from interleaving on the byte stream. +type FirmataClient struct { + connection io.ReadWriteCloser + parser FirmataParser + messages chan FirmataMessage + errors chan error + writeMu sync.Mutex + requestMu sync.Mutex + stateMu sync.RWMutex + terminalErr error + // terminalErrorHandler is invoked only for the first non-timeout read + // failure while the client context remains active. PeripheralManager uses it + // to turn an unexpected USB loss into one operator-facing broadcast. + terminalErrorHandler func(error) +} + +func NewFirmataClient(connection io.ReadWriteCloser) *FirmataClient { + return &FirmataClient{ + connection: connection, + messages: make(chan FirmataMessage, 16), + errors: make(chan error, 1), + } +} + +// Start begins consuming the serial stream. The caller still owns the port and +// closes it during shutdown; this makes the client usable with both real serial +// ports and deterministic in-memory test connections. +func (client *FirmataClient) Start(ctx context.Context) { + go client.readLoop(ctx) +} + +func (client *FirmataClient) readLoop(ctx context.Context) { + buffer := make([]byte, 256) + for { + count, err := client.connection.Read(buffer) + if count > 0 { + messages, parseErr := client.parser.Feed(buffer[:count]) + if parseErr != nil { + client.publishError(ctx, parseErr) + return + } + for _, message := range messages { + select { + case client.messages <- message: + case <-ctx.Done(): + return + } + } + } + if err != nil { + if errors.Is(err, io.EOF) { + // tarm/serial represents an ordinary ReadTimeout with io.EOF. A + // Firmata connection is expected to be quiet between commands, so + // treating that timeout as a closed device kills the reader before + // the next request can receive its reply. A real USB removal is + // reported by the serial driver as a non-EOF error. + select { + case <-ctx.Done(): + return + default: + continue + } + } + client.publishError(ctx, err) + return + } + + select { + case <-ctx.Done(): + return + default: + } + } +} + +func (client *FirmataClient) publishError(ctx context.Context, err error) { + firstTerminalError, handler := client.recordTerminalError(err) + if firstTerminalError && handler != nil && ctx.Err() == nil { + handler(err) + } + + select { + case client.errors <- err: + case <-ctx.Done(): + default: + } +} + +func (client *FirmataClient) recordTerminalError(err error) (bool, func(error)) { + client.stateMu.Lock() + defer client.stateMu.Unlock() + firstTerminalError := client.terminalErr == nil + if client.terminalErr == nil { + client.terminalErr = err + } + handler := client.terminalErrorHandler + return firstTerminalError, handler +} + +// SetTerminalErrorHandler registers the one-shot observer used after a device +// has completed discovery. If the connection already failed, the observer is +// called immediately so a narrow handshake-to-registration race is not lost. +func (client *FirmataClient) SetTerminalErrorHandler(handler func(error)) { + client.stateMu.Lock() + client.terminalErrorHandler = handler + terminalErr := client.terminalErr + client.stateMu.Unlock() + if terminalErr != nil && handler != nil { + handler(terminalErr) + } +} + +func (client *FirmataClient) write(message []byte) error { + client.writeMu.Lock() + defer client.writeMu.Unlock() + client.stateMu.RLock() + terminalErr := client.terminalErr + client.stateMu.RUnlock() + if terminalErr != nil { + return fmt.Errorf("Firmata connection unavailable: %w", terminalErr) + } + + written, err := client.connection.Write(message) + if err != nil { + firstTerminalError, handler := client.recordTerminalError(err) + if firstTerminalError && handler != nil { + handler(err) + } + return err + } + if written != len(message) { + err := fmt.Errorf("short Firmata write %d/%d", written, len(message)) + firstTerminalError, handler := client.recordTerminalError(err) + if firstTerminalError && handler != nil { + handler(err) + } + return err + } + return nil +} + +func (client *FirmataClient) writeSysex(command byte, data []byte) error { + message := make([]byte, 0, len(data)+3) + message = append(message, firmataStartSysex, command) + message = append(message, data...) + message = append(message, firmataEndSysex) + return client.write(message) +} + +func (client *FirmataClient) waitFor(ctx context.Context, match func(FirmataMessage) bool) (FirmataMessage, error) { + for { + select { + case message := <-client.messages: + if match(message) { + return message, nil + } + case err := <-client.errors: + return FirmataMessage{}, err + case <-ctx.Done(): + return FirmataMessage{}, ctx.Err() + } + } +} + +func (client *FirmataClient) QueryFirmware(ctx context.Context) (FirmataFirmware, error) { + client.requestMu.Lock() + defer client.requestMu.Unlock() + + if err := client.writeSysex(firmataReportFirmware, nil); err != nil { + return FirmataFirmware{}, err + } + message, err := client.waitFor(ctx, func(message FirmataMessage) bool { + return message.Sysex && message.Command == firmataReportFirmware + }) + if err != nil { + return FirmataFirmware{}, err + } + if len(message.Data) < 2 { + return FirmataFirmware{}, errors.New("Firmata firmware response is missing version bytes") + } + name, err := DecodeFirmata7Bit(message.Data[2:]) + if err != nil { + return FirmataFirmware{}, fmt.Errorf("decode Firmata firmware name: %w", err) + } + return FirmataFirmware{Major: int(message.Data[0]), Minor: int(message.Data[1]), Name: string(name)}, nil +} + +func (client *FirmataClient) QueryCapabilities(ctx context.Context) ([][]FirmataPinCapability, error) { + client.requestMu.Lock() + defer client.requestMu.Unlock() + + if err := client.writeSysex(firmataCapabilityQuery, nil); err != nil { + return nil, err + } + message, err := client.waitFor(ctx, func(message FirmataMessage) bool { + return message.Sysex && message.Command == firmataCapabilityReply + }) + if err != nil { + return nil, err + } + return parseFirmataCapabilities(message.Data) +} + +func parseFirmataCapabilities(data []byte) ([][]FirmataPinCapability, error) { + var pins [][]FirmataPinCapability + var pin []FirmataPinCapability + for index := 0; index < len(data); { + if data[index] == 0x7F { + pins = append(pins, pin) + pin = nil + index++ + continue + } + if index+1 >= len(data) { + return nil, errors.New("Firmata capability response ends inside a mode pair") + } + pin = append(pin, FirmataPinCapability{Mode: data[index], Resolution: data[index+1]}) + index += 2 + } + if pin != nil { + return nil, errors.New("Firmata capability response is missing its final pin separator") + } + return pins, nil +} + +func (client *FirmataClient) Describe(ctx context.Context) (PeripheralDescription, error) { + client.requestMu.Lock() + defer client.requestMu.Unlock() + + if err := client.writeSysex(firmataPeripheralFeature, []byte{firmataPeripheralDescribe}); err != nil { + return PeripheralDescription{}, err + } + message, err := client.waitFor(ctx, func(message FirmataMessage) bool { + return message.Sysex && message.Command == firmataPeripheralFeature && len(message.Data) > 0 && message.Data[0] == firmataPeripheralDescription + }) + if err != nil { + return PeripheralDescription{}, err + } + + raw, err := DecodeFirmata7Bit(message.Data[1:]) + if err != nil { + return PeripheralDescription{}, fmt.Errorf("decode peripheral description: %w", err) + } + var description PeripheralDescription + if err := json.Unmarshal(raw, &description); err != nil { + return PeripheralDescription{}, fmt.Errorf("parse peripheral description: %w", err) + } + if err := description.Validate(); err != nil { + return PeripheralDescription{}, fmt.Errorf("validate peripheral description: %w", err) + } + return description, nil +} + +func (client *FirmataClient) SetPinMode(pin, mode byte) error { + return client.write([]byte{firmataSetPinMode, pin & 0x7F, mode & 0x7F}) +} + +func (client *FirmataClient) SetDigitalPin(pin byte, enabled bool) error { + value := byte(0) + if enabled { + value = 1 + } + return client.write([]byte{firmataSetDigitalPin, pin & 0x7F, value}) +} + +func (client *FirmataClient) ExtendedAnalog(pin byte, value int) error { + if value < 0 { + return fmt.Errorf("Firmata analog value cannot be negative: %d", value) + } + + payload := []byte{pin & 0x7F} + // Firmata encodes integers as many seven-bit chunks as necessary. Zero + // still needs one value byte so the receiver can distinguish it from a + // message that contains only the pin. + for { + payload = append(payload, byte(value&0x7F)) + value >>= 7 + if value == 0 { + break + } + } + return client.writeSysex(firmataExtendedAnalog, payload) +} + +func (client *FirmataClient) ConfigureServo(pin byte, minimumPulseMicroseconds, maximumPulseMicroseconds int) error { + if minimumPulseMicroseconds <= 0 || maximumPulseMicroseconds <= minimumPulseMicroseconds { + return errors.New("servo pulse range must be positive and increasing") + } + payload := []byte{ + pin & 0x7F, + byte(minimumPulseMicroseconds & 0x7F), byte((minimumPulseMicroseconds >> 7) & 0x7F), + byte(maximumPulseMicroseconds & 0x7F), byte((maximumPulseMicroseconds >> 7) & 0x7F), + } + return client.writeSysex(firmataServoConfig, payload) +} + +func (client *FirmataClient) SendPeripheralControl(controlID string, value any) error { + payload, err := json.Marshal(struct { + Control string `json:"control"` + Value any `json:"value"` + }{Control: controlID, Value: value}) + if err != nil { + return fmt.Errorf("encode peripheral control: %w", err) + } + data := append([]byte{firmataPeripheralControl}, EncodeFirmata7Bit(payload)...) + // ConfigurableFirmata on ESP32 stores at most 252 bytes including the SysEx + // feature byte. Refuse a value that the board would otherwise discard as an + // incomplete frame; this is a transport constraint, not an application-level + // text policy. + if len(data)+1 > firmataMaximumSysexDataBytes { + return fmt.Errorf("peripheral control needs %d SysEx data bytes; Firmata accepts at most %d", len(data)+1, firmataMaximumSysexDataBytes) + } + return client.writeSysex(firmataPeripheralFeature, data) +} diff --git a/pi/roverd/firmata_hardware_controllers.go b/pi/roverd/firmata_hardware_controllers.go new file mode 100644 index 00000000..d9cbe028 --- /dev/null +++ b/pi/roverd/firmata_hardware_controllers.go @@ -0,0 +1,304 @@ +package roverd + +import ( + "fmt" + "log" + "math" + "strings" + "sync" + "time" +) + +// FirmataCameraServo preserves the established logical camera movement model +// while replacing only the final physical write. The ESP32 receives ordinary +// Firmata servo configuration and angle messages, regardless of rover host. +type FirmataCameraServo struct { + cfg CameraServoConfig + client *FirmataClient + pin byte + peripheralID string + mu sync.Mutex + currentAngle float64 + desiredAngle float64 + lastMove time.Time + moving bool + stopCh chan struct{} + closed bool +} + +func newFirmataCameraServo(peripheral *managedPeripheral, declaration PeripheralCameraServo, logger *log.Logger) (*FirmataCameraServo, error) { + cfg := CameraServoConfig{ + Enabled: true, + Pin: declaration.Pin, + FreqHz: 50, + CycleLen: 20000, + MinPulseUs: declaration.MinimumPulseMicroseconds, + MaxPulseUs: declaration.MaximumPulseMicroseconds, + MinAngle: declaration.MinimumAngleDegrees, + MaxAngle: declaration.MaximumAngleDegrees, + HomeAngle: declaration.HomeAngleDegrees, + NudgeDegrees: declaration.NudgeDegrees, + AllowRawPulse: declaration.AllowRawPulse, + Invert: declaration.Inverted, + } + servo := &FirmataCameraServo{ + cfg: cfg, + client: peripheral.client, + pin: byte(declaration.Pin), + peripheralID: peripheral.metadata.ID, + stopCh: make(chan struct{}), + } + + // SERVO_CONFIG establishes the peripheral-owned pulse calibration before + // selecting servo mode. This is standard Firmata, not a rover extension. + if err := servo.client.ConfigureServo(servo.pin, cfg.MinPulseUs, cfg.MaxPulseUs); err != nil { + return nil, fmt.Errorf("configure Firmata servo: %w", err) + } + if err := servo.client.SetPinMode(servo.pin, FirmataPinModeServo); err != nil { + return nil, fmt.Errorf("select Firmata servo mode: %w", err) + } + if err := servo.setAngleLocked(cfg.HomeAngle); err != nil { + return nil, err + } + logger.Printf("camera servo using ESP32 %s pin %d (%.1f..%.1f deg)", peripheral.metadata.ID, declaration.Pin, cfg.MinAngle, cfg.MaxAngle) + return servo, nil +} + +func (servo *FirmataCameraServo) SetAngle(angle float64) error { + servo.mu.Lock() + defer servo.mu.Unlock() + return servo.setAngleLocked(angle) +} + +func (servo *FirmataCameraServo) setAngleLocked(angle float64) error { + if servo.closed { + return errorsNewControllerClosed("camera servo") + } + servo.desiredAngle = clampFloat(angle, servo.cfg.MinAngle, servo.cfg.MaxAngle) + limited := servo.rateLimitAngleLocked(servo.desiredAngle) + if err := servo.writeAngleLocked(limited); err != nil { + return err + } + servo.currentAngle = limited + if math.Abs(limited-servo.desiredAngle) > servoAngleEpsilon { + servo.startMoveLoopLocked() + } + return nil +} + +func (servo *FirmataCameraServo) Nudge(delta float64) error { + servo.mu.Lock() + defer servo.mu.Unlock() + if delta == 0 { + delta = servo.cfg.NudgeDegrees + } + return servo.setAngleLocked(servo.currentAngle + delta) +} + +func (servo *FirmataCameraServo) SetPulseWidth(micros int) error { + servo.mu.Lock() + defer servo.mu.Unlock() + if !servo.cfg.AllowRawPulse { + return fmt.Errorf("raw pulse commands disabled") + } + if micros <= 0 { + return fmt.Errorf("pulse width must be > 0") + } + pulse := clampInt(micros, servo.cfg.MinPulseUs, servo.cfg.MaxPulseUs) + return servo.setAngleLocked(servo.pulseToAngle(pulse)) +} + +func (servo *FirmataCameraServo) CurrentAngle() float64 { + servo.mu.Lock() + defer servo.mu.Unlock() + return servo.currentAngle +} + +func (servo *FirmataCameraServo) Configuration() CameraServoConfig { + return servo.cfg +} + +func (servo *FirmataCameraServo) BackendDescription() string { + return "ESP32 " + servo.peripheralID +} + +func (servo *FirmataCameraServo) Close() { + servo.mu.Lock() + defer servo.mu.Unlock() + if servo.closed { + return + } + // Returning home matches the native Pi implementation. Any write failure is + // ignored during shutdown because the serial connection may already be gone. + _ = servo.writeAngleLocked(servo.cfg.HomeAngle) + close(servo.stopCh) + servo.closed = true +} + +func (servo *FirmataCameraServo) writeAngleLocked(angle float64) error { + rangeDegrees := servo.cfg.MaxAngle - servo.cfg.MinAngle + normalized := (angle - servo.cfg.MinAngle) / rangeDegrees + normalized = math.Max(0, math.Min(1, normalized)) + if servo.cfg.Invert { + normalized = 1 - normalized + } + // Standard Firmata servo values are positions from 0 through 180. Pulse + // calibration was already supplied through SERVO_CONFIG above. + position := int(math.Round(normalized * 180)) + return servo.client.ExtendedAnalog(servo.pin, position) +} + +func (servo *FirmataCameraServo) pulseToAngle(pulse int) float64 { + normalized := float64(pulse-servo.cfg.MinPulseUs) / float64(servo.cfg.MaxPulseUs-servo.cfg.MinPulseUs) + if servo.cfg.Invert { + normalized = 1 - normalized + } + return servo.cfg.MinAngle + normalized*(servo.cfg.MaxAngle-servo.cfg.MinAngle) +} + +func (servo *FirmataCameraServo) rateLimitAngleLocked(target float64) float64 { + now := time.Now() + if servo.lastMove.IsZero() { + servo.lastMove = now + } + elapsed := now.Sub(servo.lastMove).Seconds() + if elapsed > servoStepInterval.Seconds() { + elapsed = servoStepInterval.Seconds() + } + maximumDelta := maxServoDegPerSec * elapsed + delta := target - servo.currentAngle + if math.Abs(delta) <= maximumDelta { + servo.lastMove = now + return target + } + servo.lastMove = now + if delta > 0 { + return servo.currentAngle + maximumDelta + } + return servo.currentAngle - maximumDelta +} + +func (servo *FirmataCameraServo) startMoveLoopLocked() { + if servo.moving || servo.closed { + return + } + servo.moving = true + go func() { + ticker := time.NewTicker(servoStepInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + servo.mu.Lock() + if servo.closed || math.Abs(servo.currentAngle-servo.desiredAngle) <= servoAngleEpsilon { + servo.moving = false + servo.mu.Unlock() + return + } + limited := servo.rateLimitAngleLocked(servo.desiredAngle) + if err := servo.writeAngleLocked(limited); err != nil { + // A failed serial write makes further automatic steps pointless. + // The next user command returns the connection error normally. + servo.moving = false + servo.mu.Unlock() + return + } + servo.currentAngle = limited + servo.mu.Unlock() + case <-servo.stopCh: + return + } + } + }() +} + +// FirmataToggle owns logical state exactly like GPIOToggle but sends the final +// electrical level through Firmata's standard digital-pin command. +type FirmataToggle struct { + cfg GPIOToggleConfig + name string + client *FirmataClient + pin byte + peripheralID string + mu sync.Mutex + on bool + closed bool +} + +func newFirmataToggle(name string, peripheral *managedPeripheral, declaration PeripheralDigitalRole, logger *log.Logger) (*FirmataToggle, error) { + cfg := GPIOToggleConfig{Enabled: true, GPIOPin: declaration.Pin, InitialOn: declaration.InitiallyOn, ActiveLow: declaration.ActiveLow} + toggle := &FirmataToggle{ + cfg: cfg, + name: name, + client: peripheral.client, + pin: byte(declaration.Pin), + peripheralID: peripheral.metadata.ID, + on: cfg.InitialOn, + } + if err := toggle.client.SetPinMode(toggle.pin, FirmataPinModeOutput); err != nil { + return nil, fmt.Errorf("select Firmata output mode: %w", err) + } + if err := toggle.writeLocked(toggle.on); err != nil { + return nil, fmt.Errorf("initialize Firmata output: %w", err) + } + logger.Printf("%s using ESP32 %s pin %d (initial=%v activeLow=%v)", name, peripheral.metadata.ID, declaration.Pin, cfg.InitialOn, cfg.ActiveLow) + return toggle, nil +} + +func (toggle *FirmataToggle) HandleAction(action string) error { + toggle.mu.Lock() + defer toggle.mu.Unlock() + if toggle.closed { + return errorsNewControllerClosed(toggle.name) + } + switch strings.ToLower(strings.TrimSpace(action)) { + case "", "toggle": + return toggle.setLocked(!toggle.on) + case "on": + return toggle.setLocked(true) + case "off": + return toggle.setLocked(false) + default: + return fmt.Errorf("unknown action %q", action) + } +} + +func (toggle *FirmataToggle) setLocked(on bool) error { + if err := toggle.writeLocked(on); err != nil { + return err + } + toggle.on = on + return nil +} + +func (toggle *FirmataToggle) writeLocked(on bool) error { + physicalHigh := on + if toggle.cfg.ActiveLow { + physicalHigh = !physicalHigh + } + return toggle.client.SetDigitalPin(toggle.pin, physicalHigh) +} + +func (toggle *FirmataToggle) On() bool { + toggle.mu.Lock() + defer toggle.mu.Unlock() + return toggle.on +} + +func (toggle *FirmataToggle) Configuration() GPIOToggleConfig { + return toggle.cfg +} + +func (toggle *FirmataToggle) BackendDescription() string { + return "ESP32 " + toggle.peripheralID +} + +func (toggle *FirmataToggle) Close() { + toggle.mu.Lock() + defer toggle.mu.Unlock() + toggle.closed = true +} + +func errorsNewControllerClosed(name string) error { + return fmt.Errorf("%s controller closed", name) +} diff --git a/pi/roverd/firmata_hardware_controllers_test.go b/pi/roverd/firmata_hardware_controllers_test.go new file mode 100644 index 00000000..ce798ef9 --- /dev/null +++ b/pi/roverd/firmata_hardware_controllers_test.go @@ -0,0 +1,163 @@ +package roverd + +import ( + "bytes" + "context" + "log" + "testing" +) + +func TestDisabledNativeRolesResolveToFirmataOnEveryHostBuild(t *testing.T) { + description := PeripheralDescription{ + Name: "Rover GPIO", + RoverControls: PeripheralRoverControls{ + CameraServo: &PeripheralCameraServo{ + Pin: 14, MinimumAngleDegrees: -15, MaximumAngleDegrees: 30, + HomeAngleDegrees: 0, NudgeDegrees: 2, + MinimumPulseMicroseconds: 900, MaximumPulseMicroseconds: 2100, + }, + Headlight: &PeripheralDigitalRole{Pin: 18, ActiveLow: true, InitiallyOn: true}, + Laser: &PeripheralDigitalRole{Pin: 16, ActiveLow: false, InitiallyOn: false}, + }, + Controls: []PeripheralControl{}, + } + connection := scriptedPeripheralConnection(t, description) + manager, err := discoverPeripheralManager( + context.Background(), + "/dev/roomba", + discardLogger(), + testPeripheralDiscoveryDependencies([]string{"/dev/rover-gpio"}, map[string]*scriptedConnection{"/dev/rover-gpio": connection}), + ) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + + // All native entries are disabled, exactly as they can be on either a Pi or + // laptop rover. The shared resolver must therefore select every ESP32 role. + baseline := len(connection.Bytes()) + controllers, err := ResolveRoverHardwareControllers(&Config{}, manager, discardLogger()) + if err != nil { + t.Fatalf("resolve: %v", err) + } + defer controllers.Close() + if controllers.CameraServo == nil || controllers.Headlight == nil || controllers.Laser == nil { + t.Fatalf("missing Firmata controller: %#v", controllers) + } + if !controllers.CameraServo.Configuration().Enabled || !controllers.Headlight.Configuration().Enabled || !controllers.Laser.Configuration().Enabled { + t.Fatal("ESP32-backed roles were not advertised as enabled") + } + wantHardwareBroadcast := "Rover hardware ready: camera servo via ESP32 firmata-0, headlight via ESP32 firmata-0, laser via ESP32 firmata-0." + if messages := controllers.StartupBroadcasts(); len(messages) != 1 || messages[0] != wantHardwareBroadcast { + t.Fatalf("hardware broadcasts = %#v, want %q", messages, wantHardwareBroadcast) + } + + // Initialization uses only standard Firmata: servo calibration and mode, + // followed by the home position and digital initial states. The active-low + // headlight starts logically on, so its physical output is low. + writes := connection.Bytes()[baseline:] + wantPrefix := []byte{ + firmataStartSysex, firmataServoConfig, 14, 4, 7, 52, 16, firmataEndSysex, + firmataSetPinMode, 14, FirmataPinModeServo, + firmataStartSysex, firmataExtendedAnalog, 14, 60, firmataEndSysex, + firmataSetPinMode, 18, FirmataPinModeOutput, + firmataSetDigitalPin, 18, 0, + firmataSetPinMode, 16, FirmataPinModeOutput, + firmataSetDigitalPin, 16, 0, + } + if !bytes.Equal(writes, wantPrefix) { + t.Fatalf("initial controller bytes = %v, want %v", writes, wantPrefix) + } + + baseline = len(connection.Bytes()) + if err := controllers.Headlight.HandleAction("off"); err != nil { + t.Fatalf("turn headlight off: %v", err) + } + if controllers.Headlight.On() { + t.Fatal("headlight remained logically on") + } + // Active-low means logical off becomes a high electrical output. + if got, want := connection.Bytes()[baseline:], []byte{firmataSetDigitalPin, 18, 1}; !bytes.Equal(got, want) { + t.Fatalf("headlight bytes = %v, want %v", got, want) + } +} + +func TestMissingNativeAndFirmataRolesRemainDisabled(t *testing.T) { + manager := &PeripheralManager{byID: make(map[string]*managedPeripheral)} + controllers, err := ResolveRoverHardwareControllers(&Config{}, manager, discardLogger()) + if err != nil { + t.Fatalf("resolve: %v", err) + } + if controllers.CameraServo != nil || controllers.Headlight != nil || controllers.Laser != nil { + t.Fatalf("unexpected controllers without providers: %#v", controllers) + } +} + +func TestEnabledNativeRolesWinEvenWithSeveralFirmataProviders(t *testing.T) { + roleDescription := PeripheralDescription{RoverControls: PeripheralRoverControls{ + CameraServo: &PeripheralCameraServo{}, + Headlight: &PeripheralDigitalRole{}, + Laser: &PeripheralDigitalRole{}, + }} + manager := &PeripheralManager{ + byID: make(map[string]*managedPeripheral), + peripherals: []*managedPeripheral{ + {metadata: RoverPeripheralMetadata{ID: "firmata-0"}, description: roleDescription}, + {metadata: RoverPeripheralMetadata{ID: "firmata-1"}, description: roleDescription}, + }, + } + cfg := &Config{ + CameraServo: CameraServoConfig{Enabled: true}, + Headlight: GPIOToggleConfig{Enabled: true}, + Laser: GPIOToggleConfig{Enabled: true}, + } + nativeCamera := &testCameraServoController{cfg: cfg.CameraServo} + nativeToggles := map[string]*testToggleController{} + factories := nativeHardwareControllerFactories{ + newCameraServo: func(_ CameraServoConfig, _ *log.Logger) (CameraServoController, error) { + return nativeCamera, nil + }, + newToggle: func(name string, config GPIOToggleConfig, _ *log.Logger) (ToggleController, error) { + controller := &testToggleController{cfg: config} + nativeToggles[name] = controller + return controller, nil + }, + } + + // Duplicate Firmata declarations are irrelevant when native hardware wins; + // selection must neither fail nor initialize either ESP32 provider. + controllers, err := resolveRoverHardwareControllers(cfg, manager, discardLogger(), factories) + if err != nil { + t.Fatalf("resolve native precedence: %v", err) + } + if controllers.CameraServo != nativeCamera || controllers.Headlight != nativeToggles["headlight"] || controllers.Laser != nativeToggles["laser"] { + t.Fatal("resolver did not retain native controllers") + } + messages := controllers.StartupBroadcasts() + if len(messages) != 2 || messages[0] != "Ignored ESP32 camera servo, headlight, laser because native GPIO is enabled." || messages[1] != "Rover hardware ready: camera servo via native GPIO, headlight via native GPIO, laser via native GPIO." { + t.Fatalf("native precedence broadcasts = %#v", messages) + } +} + +type testCameraServoController struct { + cfg CameraServoConfig +} + +func (controller *testCameraServoController) SetAngle(float64) error { return nil } +func (controller *testCameraServoController) Nudge(float64) error { return nil } +func (controller *testCameraServoController) SetPulseWidth(int) error { return nil } +func (controller *testCameraServoController) CurrentAngle() float64 { return 0 } +func (controller *testCameraServoController) Configuration() CameraServoConfig { return controller.cfg } +func (controller *testCameraServoController) BackendDescription() string { return "native GPIO" } +func (controller *testCameraServoController) Close() {} + +type testToggleController struct { + cfg GPIOToggleConfig + on bool +} + +func (controller *testToggleController) HandleAction(string) error { return nil } +func (controller *testToggleController) On() bool { return controller.on } +func (controller *testToggleController) Configuration() GPIOToggleConfig { return controller.cfg } +func (controller *testToggleController) BackendDescription() string { return "native GPIO" } +func (controller *testToggleController) Close() {} diff --git a/pi/roverd/firmata_test.go b/pi/roverd/firmata_test.go new file mode 100644 index 00000000..f1551208 --- /dev/null +++ b/pi/roverd/firmata_test.go @@ -0,0 +1,441 @@ +package roverd + +import ( + "bytes" + "context" + "encoding/json" + "io" + "reflect" + "sync" + "testing" + "time" +) + +func TestFirmataParserHandlesFragmentedSysex(t *testing.T) { + parser := FirmataParser{} + + first, err := parser.Feed([]byte{firmataStartSysex, firmataPeripheralFeature, firmataPeripheralDescription, 1}) + if err != nil { + t.Fatalf("first fragment: %v", err) + } + if len(first) != 0 { + t.Fatalf("first fragment unexpectedly produced %d messages", len(first)) + } + + second, err := parser.Feed([]byte{0, 2, 0, firmataEndSysex}) + if err != nil { + t.Fatalf("second fragment: %v", err) + } + want := []FirmataMessage{{ + Command: firmataPeripheralFeature, + Data: []byte{firmataPeripheralDescription, 1, 0, 2, 0}, + Sysex: true, + }} + if !reflect.DeepEqual(second, want) { + t.Fatalf("messages = %#v, want %#v", second, want) + } +} + +func TestFirmataParserReturnsSeveralMessagesFromOneRead(t *testing.T) { + parser := FirmataParser{} + messages, err := parser.Feed([]byte{ + firmataReportVersion, 2, 5, + firmataStartSysex, firmataCapabilityReply, 0x01, 0x01, 0x7F, firmataEndSysex, + firmataSetDigitalPin, 18, 1, + }) + if err != nil { + t.Fatalf("feed: %v", err) + } + if len(messages) != 3 { + t.Fatalf("got %d messages, want 3", len(messages)) + } + if messages[0].Command != firmataReportVersion || messages[1].Command != firmataCapabilityReply || messages[2].Command != firmataSetDigitalPin { + t.Fatalf("commands were not preserved in wire order: %#v", messages) + } +} + +func TestFirmataParserRejectsEightBitSysexDataAndRecovers(t *testing.T) { + parser := FirmataParser{} + if _, err := parser.Feed([]byte{firmataStartSysex, firmataPeripheralFeature, 0x80}); err == nil { + t.Fatal("expected invalid SysEx data to fail") + } + + messages, err := parser.Feed([]byte{firmataReportVersion, 2, 5}) + if err != nil { + t.Fatalf("feed after invalid SysEx: %v", err) + } + if len(messages) != 1 || messages[0].Command != firmataReportVersion { + t.Fatalf("parser did not recover: %#v", messages) + } +} + +func TestFirmataSevenBitRoundTripIncludesUTF8(t *testing.T) { + raw := []byte(`{"name":"Café lights","value":255}`) + encoded := EncodeFirmata7Bit(raw) + for index, value := range encoded { + if value&0x80 != 0 { + t.Fatalf("encoded byte %d is not seven-bit clean: 0x%02x", index, value) + } + } + decoded, err := DecodeFirmata7Bit(encoded) + if err != nil { + t.Fatalf("decode: %v", err) + } + if !bytes.Equal(decoded, raw) { + t.Fatalf("decoded %q, want %q", decoded, raw) + } +} + +func TestDecodeFirmataSevenBitRejectsMalformedPairs(t *testing.T) { + for name, encoded := range map[string][]byte{ + "odd length": {1}, + "high byte": {1, 2}, + "eight bit": {0x80, 0}, + } { + t.Run(name, func(t *testing.T) { + if _, err := DecodeFirmata7Bit(encoded); err == nil { + t.Fatal("expected malformed pair to fail") + } + }) + } +} + +func TestPeripheralDescriptionPreservesControlOrder(t *testing.T) { + raw := []byte(`{ + "name":"Test peripheral", + "controls":[ + {"id":"servo","type":"slider","name":"Servo","min":0,"max":180,"output":{"type":"servo","pin":14}}, + {"id":"lights","type":"slider","name":"Lights","min":0,"max":255,"output":{"type":"pwm","pin":18}}, + {"id":"action","type":"button","name":"Action","mode":"momentary","output":{"type":"custom"}} + ] + }`) + var description PeripheralDescription + if err := json.Unmarshal(raw, &description); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if err := description.Validate(); err != nil { + t.Fatalf("validate: %v", err) + } + want := []string{"servo", "lights", "action"} + for index, id := range want { + if description.Controls[index].ID != id { + t.Fatalf("control %d = %q, want %q", index, description.Controls[index].ID, id) + } + } +} + +func TestPeripheralDescriptionRejectsInvalidDeclarations(t *testing.T) { + minimum, maximum, pin := 10, 1, 200 + for name, description := range map[string]PeripheralDescription{ + "duplicate id": { + Name: "device", + Controls: []PeripheralControl{ + {ID: "same", Name: "First", Type: "button", Mode: "toggle", Output: PeripheralOutput{Type: "custom"}}, + {ID: "same", Name: "Second", Type: "button", Mode: "toggle", Output: PeripheralOutput{Type: "custom"}}, + }, + }, + "reversed range": { + Name: "device", + Controls: []PeripheralControl{{ + ID: "level", Name: "Level", Type: "slider", Minimum: &minimum, Maximum: &maximum, Output: PeripheralOutput{Type: "custom"}, + }}, + }, + "pin outside Firmata": { + Name: "device", + Controls: []PeripheralControl{{ + ID: "switch", Name: "Switch", Type: "button", Mode: "toggle", Output: PeripheralOutput{Type: "digital", Pin: &pin}, + }}, + }, + } { + t.Run(name, func(t *testing.T) { + if err := description.Validate(); err == nil { + t.Fatal("expected invalid description to fail") + } + }) + } +} + +func TestParseFirmataCapabilities(t *testing.T) { + pins, err := parseFirmataCapabilities([]byte{ + FirmataPinModeOutput, 1, FirmataPinModePWM, 8, 0x7F, + FirmataPinModeOutput, 1, FirmataPinModeServo, 14, 0x7F, + }) + if err != nil { + t.Fatalf("parse capabilities: %v", err) + } + if len(pins) != 2 || len(pins[0]) != 2 || pins[1][1].Mode != FirmataPinModeServo { + t.Fatalf("unexpected capabilities: %#v", pins) + } + + if _, err := parseFirmataCapabilities([]byte{FirmataPinModeOutput}); err == nil { + t.Fatal("expected incomplete capability pair to fail") + } +} + +func TestFirmataClientWritesStandardCommands(t *testing.T) { + connection := &recordingConnection{} + client := NewFirmataClient(connection) + + if err := client.SetPinMode(14, FirmataPinModeServo); err != nil { + t.Fatalf("set pin mode: %v", err) + } + if err := client.ConfigureServo(14, 900, 2100); err != nil { + t.Fatalf("configure servo: %v", err) + } + if err := client.ExtendedAnalog(14, 180); err != nil { + t.Fatalf("extended analog: %v", err) + } + if err := client.SetDigitalPin(19, true); err != nil { + t.Fatalf("digital write: %v", err) + } + + want := []byte{ + firmataSetPinMode, 14, FirmataPinModeServo, + firmataStartSysex, firmataServoConfig, 14, 4, 7, 52, 16, firmataEndSysex, + firmataStartSysex, firmataExtendedAnalog, 14, 52, 1, firmataEndSysex, + firmataSetDigitalPin, 19, 1, + } + if got := connection.Bytes(); !bytes.Equal(got, want) { + t.Fatalf("wire bytes = %v, want %v", got, want) + } +} + +func TestFirmataClientQueriesAndDecodesDescription(t *testing.T) { + descriptionJSON := []byte(`{"name":"Bench device","controls":[{"id":"go","type":"button","name":"Go","mode":"momentary","output":{"type":"custom"}}]}`) + firmwareName := EncodeFirmata7Bit([]byte("RoverPeripheralFirmata")) + description := append([]byte{firmataStartSysex, firmataPeripheralFeature, firmataPeripheralDescription}, EncodeFirmata7Bit(descriptionJSON)...) + description = append(description, firmataEndSysex) + + connection := newScriptedConnection( + append(append([]byte{firmataStartSysex, firmataReportFirmware, 1, 0}, firmwareName...), firmataEndSysex), + description, + ) + client := NewFirmataClient(connection) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + client.Start(ctx) + + firmware, err := client.QueryFirmware(ctx) + if err != nil { + t.Fatalf("query firmware: %v", err) + } + if firmware.Name != "RoverPeripheralFirmata" || firmware.Major != 1 || firmware.Minor != 0 { + t.Fatalf("unexpected firmware: %#v", firmware) + } + + got, err := client.Describe(ctx) + if err != nil { + t.Fatalf("describe: %v", err) + } + if got.Name != "Bench device" || len(got.Controls) != 1 || got.Controls[0].ID != "go" { + t.Fatalf("unexpected description: %#v", got) + } + + writes := connection.Bytes() + wantWrites := []byte{ + firmataStartSysex, firmataReportFirmware, firmataEndSysex, + firmataStartSysex, firmataPeripheralFeature, firmataPeripheralDescribe, firmataEndSysex, + } + if !bytes.Equal(writes, wantWrites) { + t.Fatalf("queries = %v, want %v", writes, wantWrites) + } +} + +func TestFirmataClientKeepsReadingAfterSerialTimeoutEOF(t *testing.T) { + firmwareName := EncodeFirmata7Bit([]byte("RoverPeripheralFirmata")) + response := append([]byte{firmataStartSysex, firmataReportFirmware, 1, 0}, firmwareName...) + response = append(response, firmataEndSysex) + + // tarm/serial returns io.EOF when its ReadTimeout expires without bytes. + // Reproducing that behavior before the response prevents this regression + // from being hidden by an in-memory reader that blocks indefinitely instead. + connection := newScriptedConnection(response) + connection.timeoutsBeforeRead = 1 + client := NewFirmataClient(connection) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + client.Start(ctx) + + firmware, err := client.QueryFirmware(ctx) + if err != nil { + t.Fatalf("query firmware after timeout: %v", err) + } + if firmware.Name != "RoverPeripheralFirmata" { + t.Fatalf("firmware name = %q", firmware.Name) + } +} + +func TestFirmataClientEncodesCustomControl(t *testing.T) { + for name, testCase := range map[string]struct { + controlID string + value any + wantJSON string + }{ + "button": {controlID: "specialAction", value: true, wantJSON: `{"control":"specialAction","value":true}`}, + "text": {controlID: "displayText", value: "Café ready", wantJSON: `{"control":"displayText","value":"Café ready"}`}, + } { + t.Run(name, func(t *testing.T) { + connection := &recordingConnection{} + client := NewFirmataClient(connection) + if err := client.SendPeripheralControl(testCase.controlID, testCase.value); err != nil { + t.Fatalf("send control: %v", err) + } + + wire := connection.Bytes() + if len(wire) < 5 || wire[0] != firmataStartSysex || wire[1] != firmataPeripheralFeature || wire[2] != firmataPeripheralControl || wire[len(wire)-1] != firmataEndSysex { + t.Fatalf("invalid control frame: %v", wire) + } + raw, err := DecodeFirmata7Bit(wire[3 : len(wire)-1]) + if err != nil { + t.Fatalf("decode control: %v", err) + } + if string(raw) != testCase.wantJSON { + t.Fatalf("control JSON = %s, want %s", raw, testCase.wantJSON) + } + }) + } +} + +func TestFirmataClientQueriesCapabilities(t *testing.T) { + response := []byte{ + firmataStartSysex, firmataCapabilityReply, + FirmataPinModeOutput, 1, FirmataPinModePWM, 8, 0x7F, + FirmataPinModeOutput, 1, FirmataPinModeServo, 14, 0x7F, + firmataEndSysex, + } + connection := newScriptedConnection(response) + client := NewFirmataClient(connection) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + client.Start(ctx) + + pins, err := client.QueryCapabilities(ctx) + if err != nil { + t.Fatalf("query capabilities: %v", err) + } + if len(pins) != 2 || pins[0][1].Mode != FirmataPinModePWM || pins[1][1].Mode != FirmataPinModeServo { + t.Fatalf("unexpected capabilities: %#v", pins) + } + if want := []byte{firmataStartSysex, firmataCapabilityQuery, firmataEndSysex}; !bytes.Equal(connection.Bytes(), want) { + t.Fatalf("query bytes = %v, want %v", connection.Bytes(), want) + } +} + +func TestFirmataClientRejectsControlTooLargeForFirmwareParser(t *testing.T) { + connection := &recordingConnection{} + client := NewFirmataClient(connection) + if err := client.SendPeripheralControl("displayText", string(bytes.Repeat([]byte{'x'}, 200))); err == nil { + t.Fatal("expected oversized control to fail") + } + if len(connection.Bytes()) != 0 { + t.Fatalf("oversized control wrote bytes: %v", connection.Bytes()) + } +} + +// recordingConnection is deliberately minimal: write-focused tests should not +// need goroutines or a real serial device merely to inspect exact Firmata bytes. +type recordingConnection struct { + mu sync.Mutex + writes bytes.Buffer + closed bool + writeErr error +} + +func (connection *recordingConnection) Read(_ []byte) (int, error) { return 0, io.EOF } + +func (connection *recordingConnection) Write(data []byte) (int, error) { + connection.mu.Lock() + defer connection.mu.Unlock() + if connection.closed { + return 0, io.ErrClosedPipe + } + if connection.writeErr != nil { + return 0, connection.writeErr + } + return connection.writes.Write(data) +} + +func (connection *recordingConnection) Close() error { + connection.mu.Lock() + defer connection.mu.Unlock() + connection.closed = true + return nil +} + +func (connection *recordingConnection) Bytes() []byte { + connection.mu.Lock() + defer connection.mu.Unlock() + return append([]byte(nil), connection.writes.Bytes()...) +} + +func (connection *recordingConnection) Closed() bool { + connection.mu.Lock() + defer connection.mu.Unlock() + return connection.closed +} + +func (connection *recordingConnection) SetWriteError(err error) { + connection.mu.Lock() + defer connection.mu.Unlock() + connection.writeErr = err +} + +// scriptedConnection releases one response after each client write. This +// mirrors request/response serial behavior and prevents a fast reader goroutine +// from publishing all scripted answers before the matching query is sent. +type scriptedConnection struct { + recordingConnection + responses chan []byte + reads chan []byte + timeoutsBeforeRead int + pendingRead []byte + closeOnce sync.Once +} + +func newScriptedConnection(responses ...[]byte) *scriptedConnection { + connection := &scriptedConnection{ + responses: make(chan []byte, len(responses)), + reads: make(chan []byte, len(responses)), + } + for _, response := range responses { + connection.responses <- append([]byte(nil), response...) + } + return connection +} + +func (connection *scriptedConnection) Read(target []byte) (int, error) { + if connection.timeoutsBeforeRead > 0 { + connection.timeoutsBeforeRead-- + return 0, io.EOF + } + if len(connection.pendingRead) == 0 { + response, ok := <-connection.reads + if !ok { + return 0, io.ErrClosedPipe + } + connection.pendingRead = response + } + written := copy(target, connection.pendingRead) + connection.pendingRead = connection.pendingRead[written:] + return written, nil +} + +func (connection *scriptedConnection) Write(data []byte) (int, error) { + written, err := connection.recordingConnection.Write(data) + if err == nil { + select { + case response := <-connection.responses: + connection.reads <- response + default: + } + } + return written, err +} + +func (connection *scriptedConnection) Close() error { + connection.closeOnce.Do(func() { + _ = connection.recordingConnection.Close() + close(connection.reads) + }) + return nil +} diff --git a/pi/roverd/gpio_toggle.go b/pi/roverd/gpio_toggle.go index 43d1c9f7..cf30c4b4 100644 --- a/pi/roverd/gpio_toggle.go +++ b/pi/roverd/gpio_toggle.go @@ -87,6 +87,15 @@ func (g *GPIOToggle) On() bool { return g.on } +// Configuration returns the native toggle behavior used in the rover hello. +func (g *GPIOToggle) Configuration() GPIOToggleConfig { + return g.cfg +} + +func (g *GPIOToggle) BackendDescription() string { + return "native GPIO" +} + func (g *GPIOToggle) setLocked(on bool) error { // This is the only place a logical device state becomes an electrical GPIO // value. Hardware that turns on when pulled low sets activeLow in roverd diff --git a/pi/roverd/gpio_toggle_debian_laptop.go b/pi/roverd/gpio_toggle_debian_laptop.go index 71bfabed..3b796020 100644 --- a/pi/roverd/gpio_toggle_debian_laptop.go +++ b/pi/roverd/gpio_toggle_debian_laptop.go @@ -13,10 +13,10 @@ type GPIOToggle struct { func NewGPIOToggle(name string, _ GPIOToggleConfig, _ *log.Logger) (*GPIOToggle, error) { /* - A Debian laptop has no Raspberry Pi GPIO character-device contract for - headlights or lasers. Returning an error when enabled makes bad laptop - configs fail during startup instead of advertising controls that cannot - change any hardware. + A Debian laptop has no native Raspberry Pi GPIO contract. Returning an + error here catches an invalid native configuration; the shared resolver + selects an ESP32 Firmata toggle before this constructor when native GPIO + is disabled. */ return nil, fmt.Errorf("%s not supported in the debian-laptop build", name) } @@ -30,3 +30,11 @@ func (g *GPIOToggle) HandleAction(action string) error { func (g *GPIOToggle) On() bool { return false } + +func (g *GPIOToggle) Configuration() GPIOToggleConfig { + return GPIOToggleConfig{} +} + +func (g *GPIOToggle) BackendDescription() string { + return "native GPIO" +} diff --git a/pi/roverd/gpio_toggle_dummy.go b/pi/roverd/gpio_toggle_dummy.go index b7cae096..5e918686 100644 --- a/pi/roverd/gpio_toggle_dummy.go +++ b/pi/roverd/gpio_toggle_dummy.go @@ -24,3 +24,11 @@ func (g *GPIOToggle) HandleAction(action string) error { func (g *GPIOToggle) On() bool { return false } + +func (g *GPIOToggle) Configuration() GPIOToggleConfig { + return GPIOToggleConfig{} +} + +func (g *GPIOToggle) BackendDescription() string { + return "native GPIO" +} diff --git a/pi/roverd/hardware_controllers.go b/pi/roverd/hardware_controllers.go new file mode 100644 index 00000000..84b41566 --- /dev/null +++ b/pi/roverd/hardware_controllers.go @@ -0,0 +1,164 @@ +package roverd + +import ( + "fmt" + "log" + "strings" + "time" +) + +// Both physical servo backends consume these exact motion constants. Keeping +// them in shared code prevents Pi PWM and ESP32 Firmata movement from drifting +// apart as either implementation evolves. +const ( + maxServoDegPerSec = 60.0 + servoStepInterval = 20 * time.Millisecond + servoAngleEpsilon = 0.01 +) + +// CameraServoController is the hardware-neutral camera-tilt contract used by +// WSClient. Native Pi PWM and ESP32 Firmata implementations expose identical +// logical behavior, so command handling never branches on the rover host type. +type CameraServoController interface { + SetAngle(angle float64) error + Nudge(delta float64) error + SetPulseWidth(micros int) error + CurrentAngle() float64 + Configuration() CameraServoConfig + BackendDescription() string + Close() +} + +// ToggleController keeps headlight and laser command/state behavior independent +// of whether the electrical write happens on native Pi GPIO or an ESP32 pin. +type ToggleController interface { + HandleAction(action string) error + On() bool + Configuration() GPIOToggleConfig + BackendDescription() string + Close() +} + +// RoverHardwareControllers is the result of the single startup-time backend +// decision. Its effective configurations are derived from whichever backend +// won, making the normal rover hello accurate on both Pi and laptop hosts. +type RoverHardwareControllers struct { + CameraServo CameraServoController + Headlight ToggleController + Laser ToggleController + ignoredESP32Roles []string +} + +// StartupBroadcasts returns short operator-facing messages. Detailed pin and +// protocol information remains in the journal; tty1 only explains which +// physical backend won and whether an advertised ESP32 role was ignored. +func (controllers RoverHardwareControllers) StartupBroadcasts() []string { + var messages []string + if len(controllers.ignoredESP32Roles) > 0 { + messages = append(messages, fmt.Sprintf( + "Ignored ESP32 %s because native GPIO is enabled.", + strings.Join(controllers.ignoredESP32Roles, ", "), + )) + } + messages = append(messages, fmt.Sprintf( + "Rover hardware ready: camera servo via %s, headlight via %s, laser via %s.", + controllerBackend(controllers.CameraServo), + controllerBackend(controllers.Headlight), + controllerBackend(controllers.Laser), + )) + return messages +} + +func controllerBackend(controller interface{ BackendDescription() string }) string { + if controller == nil { + return "disabled" + } + return controller.BackendDescription() +} + +type nativeHardwareControllerFactories struct { + newCameraServo func(CameraServoConfig, *log.Logger) (CameraServoController, error) + newToggle func(string, GPIOToggleConfig, *log.Logger) (ToggleController, error) +} + +// ResolveRoverHardwareControllers applies one rule on every real rover build: +// enabled native GPIO wins, otherwise one discovered ESP32 may fill the role. +// The rule is intentionally not selected by GOARCH or the debian_laptop tag. +func ResolveRoverHardwareControllers(cfg *Config, peripherals *PeripheralManager, logger *log.Logger) (RoverHardwareControllers, error) { + factories := nativeHardwareControllerFactories{ + newCameraServo: func(config CameraServoConfig, logger *log.Logger) (CameraServoController, error) { + return NewCameraServo(config, logger) + }, + newToggle: func(name string, config GPIOToggleConfig, logger *log.Logger) (ToggleController, error) { + return NewGPIOToggle(name, config, logger) + }, + } + return resolveRoverHardwareControllers(cfg, peripherals, logger, factories) +} + +func resolveRoverHardwareControllers(cfg *Config, peripherals *PeripheralManager, logger *log.Logger, factories nativeHardwareControllerFactories) (RoverHardwareControllers, error) { + var controllers RoverHardwareControllers + var err error + // Record ignored declarations separately from selecting controllers so the + // same native-first decision can be explained on the local rover console. + if cfg.CameraServo.Enabled && peripherals.HasRoverRole("cameraServo") { + controllers.ignoredESP32Roles = append(controllers.ignoredESP32Roles, "camera servo") + } + if cfg.Headlight.Enabled && peripherals.HasRoverRole("headlight") { + controllers.ignoredESP32Roles = append(controllers.ignoredESP32Roles, "headlight") + } + if cfg.Laser.Enabled && peripherals.HasRoverRole("laser") { + controllers.ignoredESP32Roles = append(controllers.ignoredESP32Roles, "laser") + } + + controllers.CameraServo, err = resolveCameraServoController(cfg.CameraServo, peripherals, logger, factories.newCameraServo) + if err != nil { + return RoverHardwareControllers{}, fmt.Errorf("init camera servo: %w", err) + } + controllers.Headlight, err = resolveToggleController("headlight", cfg.Headlight, peripherals, logger, factories.newToggle) + if err != nil { + controllers.Close() + return RoverHardwareControllers{}, fmt.Errorf("init headlight: %w", err) + } + controllers.Laser, err = resolveToggleController("laser", cfg.Laser, peripherals, logger, factories.newToggle) + if err != nil { + controllers.Close() + return RoverHardwareControllers{}, fmt.Errorf("init laser: %w", err) + } + return controllers, nil +} + +func resolveCameraServoController(nativeConfig CameraServoConfig, peripherals *PeripheralManager, logger *log.Logger, newNative func(CameraServoConfig, *log.Logger) (CameraServoController, error)) (CameraServoController, error) { + if nativeConfig.Enabled { + if peripherals.HasRoverRole("cameraServo") { + logger.Printf("ignoring ESP32 cameraServo because native camera servo is enabled") + } + return newNative(nativeConfig, logger) + } + return peripherals.NewFirmataCameraServo(logger) +} + +func resolveToggleController(name string, nativeConfig GPIOToggleConfig, peripherals *PeripheralManager, logger *log.Logger, newNative func(string, GPIOToggleConfig, *log.Logger) (ToggleController, error)) (ToggleController, error) { + if nativeConfig.Enabled { + if peripherals.HasRoverRole(name) { + logger.Printf("ignoring ESP32 %s because native %s is enabled", name, name) + } + return newNative(name, nativeConfig, logger) + } + return peripherals.NewFirmataToggle(name, logger) +} + +// Close releases selected controller resources in reverse dependency order. +// Firmata controllers do not close the shared serial connection; that remains +// owned by PeripheralManager and is released by its separate shutdown defer. +func (controllers *RoverHardwareControllers) Close() { + if controllers.Laser != nil { + controllers.Laser.Close() + } + if controllers.Headlight != nil { + controllers.Headlight.Close() + } + if controllers.CameraServo != nil { + controllers.CameraServo.Close() + } +} diff --git a/pi/roverd/peripheral_command_test.go b/pi/roverd/peripheral_command_test.go new file mode 100644 index 00000000..679ff99a --- /dev/null +++ b/pi/roverd/peripheral_command_test.go @@ -0,0 +1,57 @@ +package roverd + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestHelloPeripheralMetadataContainsOnlyRenderableFields(t *testing.T) { + minimum, maximum := 0, 180 + message := helloMessage{ + Type: "hello", + Name: "test-rover", + Peripherals: []RoverPeripheralMetadata{{ + ID: "firmata-0", + Name: "Camera arm", + Controls: []RoverPeripheralControl{{ + ID: "position", Type: "slider", Name: "Position", Minimum: &minimum, Maximum: &maximum, + }}, + }}, + } + + encoded, err := json.Marshal(message) + if err != nil { + t.Fatalf("marshal hello: %v", err) + } + text := string(encoded) + if !strings.Contains(text, `"peripherals":[{"id":"firmata-0","name":"Camera arm","controls":[{"id":"position","type":"slider","name":"Position","min":0,"max":180}]`) { + t.Fatalf("hello is missing ordered peripheral metadata: %s", text) + } + var envelope map[string]json.RawMessage + if err := json.Unmarshal(encoded, &envelope); err != nil { + t.Fatalf("unmarshal hello envelope: %v", err) + } + peripheralJSON := string(envelope["peripherals"]) + if strings.Contains(peripheralJSON, `"pin"`) || strings.Contains(peripheralJSON, `"output"`) { + t.Fatalf("hello exposed private Firmata routing: %s", peripheralJSON) + } +} + +func TestInboundPeripheralCommandPreservesRawJSONValue(t *testing.T) { + var message inboundMessage + err := json.Unmarshal([]byte(`{ + "type":"peripheral", + "id":"command-1", + "peripheral":{"id":"firmata-0","control":"displayText","value":"hello rover"} + }`), &message) + if err != nil { + t.Fatalf("unmarshal command: %v", err) + } + if message.Peripheral == nil || message.Peripheral.ID != "firmata-0" || message.Peripheral.Control != "displayText" { + t.Fatalf("unexpected peripheral command: %#v", message.Peripheral) + } + if string(message.Peripheral.Value) != `"hello rover"` { + t.Fatalf("raw value = %s", message.Peripheral.Value) + } +} diff --git a/pi/roverd/peripheral_discovery_dummy.go b/pi/roverd/peripheral_discovery_dummy.go new file mode 100644 index 00000000..e37c190c --- /dev/null +++ b/pi/roverd/peripheral_discovery_dummy.go @@ -0,0 +1,24 @@ +//go:build dummy + +package roverd + +import ( + "context" + "io" + "log" + "time" +) + +// DiscoverPeripheralManager remains inert in a dummy build. The dummy daemon is +// specifically used without rover hardware and must not probe or reset serial +// devices that happen to be attached to a developer's machine. +func DiscoverPeripheralManager(ctx context.Context, excludedDevice string, logger *log.Logger) (*PeripheralManager, error) { + dependencies := peripheralDiscoveryDependencies{ + listCandidates: func(string) ([]string, error) { return nil, nil }, + open: func(string) (io.ReadWriteCloser, error) { return nil, nil }, + sleep: func(time.Duration) {}, + startupWait: 0, + handshakeWait: 0, + } + return discoverPeripheralManager(ctx, excludedDevice, logger, dependencies) +} diff --git a/pi/roverd/peripheral_discovery_serial.go b/pi/roverd/peripheral_discovery_serial.go new file mode 100644 index 00000000..f012a709 --- /dev/null +++ b/pi/roverd/peripheral_discovery_serial.go @@ -0,0 +1,38 @@ +//go:build !dummy + +package roverd + +import ( + "context" + "io" + "log" + "time" + + "github.com/tarm/serial" +) + +const ( + peripheralBaud = 115200 + peripheralReadTimeout = 100 * time.Millisecond +) + +// DiscoverPeripheralManager performs the one and only peripheral scan for this +// roverd process. The Roomba Open Interface serial device is explicitly +// excluded because it belongs to SerialAdapter and must never be probed as an +// ESP32 peripheral. +func DiscoverPeripheralManager(ctx context.Context, excludedDevice string, logger *log.Logger) (*PeripheralManager, error) { + dependencies := peripheralDiscoveryDependencies{ + listCandidates: listPeripheralCandidates, + open: func(devicePath string) (io.ReadWriteCloser, error) { + return serial.OpenPort(&serial.Config{ + Name: devicePath, + Baud: peripheralBaud, + ReadTimeout: peripheralReadTimeout, + }) + }, + sleep: time.Sleep, + startupWait: peripheralStartupWait, + handshakeWait: peripheralHandshakeTimeout, + } + return discoverPeripheralManager(ctx, excludedDevice, logger, dependencies) +} diff --git a/pi/roverd/peripheral_manager.go b/pi/roverd/peripheral_manager.go new file mode 100644 index 00000000..81456435 --- /dev/null +++ b/pi/roverd/peripheral_manager.go @@ -0,0 +1,584 @@ +package roverd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "path/filepath" + "sort" + "strings" + "sync" + "time" + "unicode/utf8" +) + +const ( + peripheralStartupWait = 2 * time.Second + peripheralHandshakeTimeout = 5 * time.Second + peripheralFirmwareName = "RoverPeripheralFirmata" +) + +// RoverPeripheralMetadata is the part of a peripheral description that leaves +// roverd. Pin numbers and output mappings intentionally remain private to the +// rover process; the server and browser identify only the declared control. +type RoverPeripheralMetadata struct { + ID string `json:"id"` + Name string `json:"name"` + Controls []RoverPeripheralControl `json:"controls"` +} + +// RoverPeripheralControl contains only fields needed to render and operate one +// of the four generic UI controls. Pointer fields preserve legitimate zero +// bounds while still omitting properties that do not apply to a control type. +type RoverPeripheralControl struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Mode string `json:"mode,omitempty"` + Minimum *int `json:"min,omitempty"` + Maximum *int `json:"max,omitempty"` + MaximumLength *int `json:"maxLength,omitempty"` +} + +type managedPeripheral struct { + metadata RoverPeripheralMetadata + description PeripheralDescription + controls map[string]PeripheralControl + client *FirmataClient + connection io.ReadWriteCloser + devicePath string + capabilities [][]FirmataPinCapability +} + +// PeripheralManager owns the immutable boot-time inventory and every serial +// connection behind it. The inventory never changes after discovery, even if a +// USB device later disappears; a process restart is the only rescan mechanism. +type PeripheralManager struct { + mu sync.RWMutex + peripherals []*managedPeripheral + byID map[string]*managedPeripheral + cancel context.CancelFunc + closeOnce sync.Once + logger *log.Logger + failures chan PeripheralFailure +} + +// PeripheralFailure is emitted once when a successfully discovered device's +// serial reader terminates unexpectedly. Device identity is retained even +// though reconnection still requires restarting roverd. +type PeripheralFailure struct { + ID string + Name string + Err error +} + +type peripheralDiscoveryDependencies struct { + listCandidates func(excludedDevice string) ([]string, error) + open func(devicePath string) (io.ReadWriteCloser, error) + sleep func(time.Duration) + startupWait time.Duration + handshakeWait time.Duration +} + +func discoverPeripheralManager(ctx context.Context, excludedDevice string, logger *log.Logger, dependencies peripheralDiscoveryDependencies) (*PeripheralManager, error) { + managerContext, cancel := context.WithCancel(ctx) + manager := &PeripheralManager{ + byID: make(map[string]*managedPeripheral), + cancel: cancel, + logger: logger, + failures: make(chan PeripheralFailure, 16), + } + + candidates, err := dependencies.listCandidates(excludedDevice) + if err != nil { + manager.Close() + return nil, fmt.Errorf("list peripheral serial devices: %w", err) + } + + for _, devicePath := range candidates { + connection, err := dependencies.open(devicePath) + if err != nil { + logger.Printf("skipping peripheral candidate %s: open failed: %v", devicePath, err) + continue + } + + // UART bridge and native-USB development boards may reset when opened. + // Waiting and then draining boot fragments gives the handshake a fresh + // parser boundary instead of occasionally starting inside an old SysEx. + dependencies.sleep(dependencies.startupWait) + if err := drainPeripheralSerial(connection); err != nil { + connection.Close() + logger.Printf("skipping peripheral candidate %s: drain failed: %v", devicePath, err) + continue + } + + client := NewFirmataClient(connection) + client.Start(managerContext) + firmware, err := queryPeripheralFirmware(managerContext, client, dependencies.handshakeWait) + if err != nil { + connection.Close() + logger.Printf("skipping peripheral candidate %s: Firmata query failed: %v", devicePath, err) + continue + } + if firmware.Name != peripheralFirmwareName { + connection.Close() + logger.Printf("skipping Firmata device %s: firmware %q does not expose rover peripherals", devicePath, firmware.Name) + continue + } + + capabilities, err := queryPeripheralCapabilities(managerContext, client, dependencies.handshakeWait) + if err != nil { + connection.Close() + manager.Close() + return nil, fmt.Errorf("query capabilities from rover peripheral %s: %w", devicePath, err) + } + description, err := queryPeripheralDescription(managerContext, client, dependencies.handshakeWait) + if err != nil { + connection.Close() + manager.Close() + return nil, fmt.Errorf("describe rover peripheral %s: %w", devicePath, err) + } + + peripheral := newManagedPeripheral(len(manager.peripherals), devicePath, connection, client, description, capabilities) + if err := peripheral.initializeStandardOutputs(); err != nil { + connection.Close() + manager.Close() + return nil, fmt.Errorf("initialize rover peripheral %s: %w", devicePath, err) + } + manager.peripherals = append(manager.peripherals, peripheral) + manager.byID[peripheral.metadata.ID] = peripheral + client.SetTerminalErrorHandler(func(terminalErr error) { + failure := PeripheralFailure{ID: peripheral.metadata.ID, Name: peripheral.metadata.Name, Err: terminalErr} + select { + case manager.failures <- failure: + default: + // The channel is intentionally bounded because broadcasts are + // diagnostic. Never block a Firmata reader during a fleet-wide + // shutdown or an unlikely burst of simultaneous USB failures. + logger.Printf("peripheral failure notification queue full for %s: %v", peripheral.metadata.ID, terminalErr) + } + }) + logger.Printf("discovered rover peripheral %s on %s with %d generic controls", description.Name, devicePath, len(description.Controls)) + } + + return manager, nil +} + +// StartupBroadcasts describes the fixed inventory without exposing device +// paths or wiring details on the rover's local console. +func (manager *PeripheralManager) StartupBroadcasts() []string { + inventory := manager.Inventory() + if len(inventory) == 0 { + return []string{"No ESP32 rover peripherals detected during startup."} + } + messages := make([]string, 0, len(inventory)) + for _, peripheral := range inventory { + messages = append(messages, fmt.Sprintf( + "Rover peripheral %q connected as %s with %d additional controls.", + peripheral.Name, + peripheral.ID, + len(peripheral.Controls), + )) + } + return messages +} + +// Failures exposes unexpected runtime disconnects to the daemon entry point, +// which owns the ConsoleNotifier and therefore owns user-facing wording. +func (manager *PeripheralManager) Failures() <-chan PeripheralFailure { + if manager == nil { + return nil + } + return manager.failures +} + +func listPeripheralCandidates(excludedDevice string) ([]string, error) { + patterns := []string{ + "/dev/serial/by-id/*", + "/dev/ttyUSB*", + "/dev/ttyACM*", + } + var matchesInPreferenceOrder []string + + for _, pattern := range patterns { + matches, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + sort.Strings(matches) + matchesInPreferenceOrder = append(matchesInPreferenceOrder, matches...) + } + return uniquePeripheralCandidates(matchesInPreferenceOrder, excludedDevice), nil +} + +func uniquePeripheralCandidates(matches []string, excludedDevice string) []string { + excludedCanonical := canonicalDevicePath(excludedDevice) + seen := make(map[string]struct{}) + var candidates []string + for _, match := range matches { + canonical := canonicalDevicePath(match) + if canonical == excludedCanonical { + continue + } + if _, exists := seen[canonical]; exists { + continue + } + seen[canonical] = struct{}{} + // /dev/serial/by-id matches are passed first, so retaining the first + // spelling favors stable names while still removing each tty alias. + candidates = append(candidates, match) + } + return candidates +} + +func canonicalDevicePath(devicePath string) string { + if devicePath == "" { + return "" + } + resolved, err := filepath.EvalSymlinks(devicePath) + if err == nil { + return resolved + } + abs, err := filepath.Abs(devicePath) + if err == nil { + return filepath.Clean(abs) + } + return filepath.Clean(devicePath) +} + +func drainPeripheralSerial(connection io.Reader) error { + buffer := make([]byte, 256) + for { + _, err := connection.Read(buffer) + if errors.Is(err, io.EOF) { + // tarm/serial uses EOF to mean its short read timeout elapsed. That + // quiet interval is precisely the boundary needed before handshaking. + return nil + } + if err != nil { + return err + } + } +} + +func queryPeripheralFirmware(ctx context.Context, client *FirmataClient, timeout time.Duration) (FirmataFirmware, error) { + queryContext, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return client.QueryFirmware(queryContext) +} + +func queryPeripheralCapabilities(ctx context.Context, client *FirmataClient, timeout time.Duration) ([][]FirmataPinCapability, error) { + queryContext, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return client.QueryCapabilities(queryContext) +} + +func queryPeripheralDescription(ctx context.Context, client *FirmataClient, timeout time.Duration) (PeripheralDescription, error) { + queryContext, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + return client.Describe(queryContext) +} + +func newManagedPeripheral(index int, devicePath string, connection io.ReadWriteCloser, client *FirmataClient, description PeripheralDescription, capabilities [][]FirmataPinCapability) *managedPeripheral { + controls := make(map[string]PeripheralControl, len(description.Controls)) + metadataControls := make([]RoverPeripheralControl, 0, len(description.Controls)) + for _, control := range description.Controls { + controls[control.ID] = control + metadataControls = append(metadataControls, RoverPeripheralControl{ + ID: control.ID, + Type: control.Type, + Name: control.Name, + Mode: control.Mode, + Minimum: cloneIntPointer(control.Minimum), + Maximum: cloneIntPointer(control.Maximum), + MaximumLength: cloneIntPointer(control.MaximumLength), + }) + } + + return &managedPeripheral{ + metadata: RoverPeripheralMetadata{ + ID: fmt.Sprintf("firmata-%d", index), + Name: description.Name, + Controls: metadataControls, + }, + description: description, + controls: controls, + client: client, + connection: connection, + devicePath: devicePath, + capabilities: capabilities, + } +} + +func cloneIntPointer(value *int) *int { + if value == nil { + return nil + } + cloned := *value + return &cloned +} + +func (peripheral *managedPeripheral) initializeStandardOutputs() error { + if camera := peripheral.description.RoverControls.CameraServo; camera != nil { + if err := peripheral.requirePinMode("cameraServo", camera.Pin, FirmataPinModeServo); err != nil { + return err + } + } + if headlight := peripheral.description.RoverControls.Headlight; headlight != nil { + if err := peripheral.requirePinMode("headlight", headlight.Pin, FirmataPinModeOutput); err != nil { + return err + } + } + if laser := peripheral.description.RoverControls.Laser; laser != nil { + if err := peripheral.requirePinMode("laser", laser.Pin, FirmataPinModeOutput); err != nil { + return err + } + } + + for _, control := range peripheral.description.Controls { + if control.Output.Type == "custom" { + continue + } + pin := byte(*control.Output.Pin) + requiredMode := FirmataPinModeOutput + if control.Output.Type == "pwm" { + requiredMode = FirmataPinModePWM + } else if control.Output.Type == "servo" { + requiredMode = FirmataPinModeServo + } + if err := peripheral.requirePinMode("control "+control.ID, int(pin), requiredMode); err != nil { + return err + } + switch control.Output.Type { + case "digital": + if err := peripheral.client.SetPinMode(pin, FirmataPinModeOutput); err != nil { + return fmt.Errorf("configure control %q as digital: %w", control.ID, err) + } + // A generic button begins logically off. Active-low hardware needs a + // high electrical level to represent that same initial state. + if err := peripheral.client.SetDigitalPin(pin, control.Output.ActiveLow); err != nil { + return fmt.Errorf("initialize digital control %q: %w", control.ID, err) + } + case "pwm": + if err := peripheral.client.SetPinMode(pin, FirmataPinModePWM); err != nil { + return fmt.Errorf("configure control %q as PWM: %w", control.ID, err) + } + case "servo": + if err := peripheral.client.SetPinMode(pin, FirmataPinModeServo); err != nil { + return fmt.Errorf("configure control %q as servo: %w", control.ID, err) + } + } + } + return nil +} + +func (peripheral *managedPeripheral) requirePinMode(owner string, pin int, requiredMode byte) error { + if pin < 0 || pin >= len(peripheral.capabilities) { + return fmt.Errorf("%s advertises pin %d, but Firmata reported only %d pins", owner, pin, len(peripheral.capabilities)) + } + for _, capability := range peripheral.capabilities[pin] { + if capability.Mode == requiredMode { + return nil + } + } + return fmt.Errorf("%s advertises pin %d without required Firmata mode 0x%02x", owner, pin, requiredMode) +} + +// HasRoverRole reports whether discovery found an ESP32 implementation of one +// established rover control. It is used only for startup selection and logging; +// commands continue to target the selected controller interface directly. +func (manager *PeripheralManager) HasRoverRole(role string) bool { + return len(manager.roverRoleProviders(role)) > 0 +} + +func (manager *PeripheralManager) roverRoleProviders(role string) []*managedPeripheral { + if manager == nil { + return nil + } + manager.mu.RLock() + defer manager.mu.RUnlock() + var providers []*managedPeripheral + for _, peripheral := range manager.peripherals { + switch role { + case "cameraServo": + if peripheral.description.RoverControls.CameraServo != nil { + providers = append(providers, peripheral) + } + case "headlight": + if peripheral.description.RoverControls.Headlight != nil { + providers = append(providers, peripheral) + } + case "laser": + if peripheral.description.RoverControls.Laser != nil { + providers = append(providers, peripheral) + } + } + } + return providers +} + +// NewFirmataCameraServo constructs the shared camera controller only when a +// discovered peripheral declared that standardized role. Absence is a normal +// disabled-feature result rather than an error. +func (manager *PeripheralManager) NewFirmataCameraServo(logger *log.Logger) (CameraServoController, error) { + providers := manager.roverRoleProviders("cameraServo") + if len(providers) == 0 { + return nil, nil + } + if len(providers) > 1 { + return nil, duplicateRoverRoleError("cameraServo", providers) + } + peripheral := providers[0] + return newFirmataCameraServo(peripheral, *peripheral.description.RoverControls.CameraServo, logger) +} + +// NewFirmataToggle resolves either standardized digital role without exposing +// the peripheral connection or ESP32 pin to WSClient. +func (manager *PeripheralManager) NewFirmataToggle(role string, logger *log.Logger) (ToggleController, error) { + providers := manager.roverRoleProviders(role) + if len(providers) == 0 { + return nil, nil + } + if len(providers) > 1 { + return nil, duplicateRoverRoleError(role, providers) + } + peripheral := providers[0] + var declaration *PeripheralDigitalRole + switch role { + case "headlight": + declaration = peripheral.description.RoverControls.Headlight + case "laser": + declaration = peripheral.description.RoverControls.Laser + default: + return nil, fmt.Errorf("unknown Firmata toggle role %q", role) + } + return newFirmataToggle(role, peripheral, *declaration, logger) +} + +func duplicateRoverRoleError(role string, providers []*managedPeripheral) error { + providerIDs := make([]string, 0, len(providers)) + for _, provider := range providers { + providerIDs = append(providerIDs, provider.metadata.ID) + } + return fmt.Errorf("rover peripheral role %s has multiple providers: %s", role, strings.Join(providerIDs, ", ")) +} + +// Inventory returns a defensive copy in startup order. Server reconnects reuse +// this same list and therefore never cause a USB rescan or ID reassignment. +func (manager *PeripheralManager) Inventory() []RoverPeripheralMetadata { + if manager == nil { + return nil + } + manager.mu.RLock() + defer manager.mu.RUnlock() + + inventory := make([]RoverPeripheralMetadata, 0, len(manager.peripherals)) + for _, peripheral := range manager.peripherals { + metadata := peripheral.metadata + metadata.Controls = make([]RoverPeripheralControl, 0, len(peripheral.metadata.Controls)) + for _, control := range peripheral.metadata.Controls { + control.Minimum = cloneIntPointer(control.Minimum) + control.Maximum = cloneIntPointer(control.Maximum) + control.MaximumLength = cloneIntPointer(control.MaximumLength) + metadata.Controls = append(metadata.Controls, control) + } + inventory = append(inventory, metadata) + } + return inventory +} + +// SetControl validates the browser-shaped value against the ESP32 declaration, +// then uses the private output mapping selected during startup. Neither the +// server nor browser can choose a pin or switch a custom control into raw GPIO. +func (manager *PeripheralManager) SetControl(peripheralID, controlID string, rawValue json.RawMessage) error { + if manager == nil { + return errors.New("rover peripherals disabled") + } + manager.mu.RLock() + peripheral := manager.byID[peripheralID] + manager.mu.RUnlock() + if peripheral == nil { + return fmt.Errorf("unknown peripheral %q", peripheralID) + } + control, exists := peripheral.controls[controlID] + if !exists { + return fmt.Errorf("unknown control %q on peripheral %q", controlID, peripheralID) + } + + value, err := decodePeripheralControlValue(control, rawValue) + if err != nil { + return fmt.Errorf("control %q: %w", controlID, err) + } + + switch control.Output.Type { + case "digital": + enabled := value.(bool) + if control.Output.ActiveLow { + enabled = !enabled + } + return peripheral.client.SetDigitalPin(byte(*control.Output.Pin), enabled) + case "pwm", "servo": + return peripheral.client.ExtendedAnalog(byte(*control.Output.Pin), value.(int)) + case "custom": + return peripheral.client.SendPeripheralControl(control.ID, value) + default: + return fmt.Errorf("control has unsupported output %q", control.Output.Type) + } +} + +func decodePeripheralControlValue(control PeripheralControl, rawValue json.RawMessage) (any, error) { + if len(rawValue) == 0 { + return nil, errors.New("value is required") + } + + switch control.Type { + case "slider", "number": + var value int + if err := json.Unmarshal(rawValue, &value); err != nil { + return nil, errors.New("value must be a whole number") + } + if value < *control.Minimum || value > *control.Maximum { + return nil, fmt.Errorf("value must be between %d and %d", *control.Minimum, *control.Maximum) + } + return value, nil + case "button": + var value bool + if err := json.Unmarshal(rawValue, &value); err != nil { + return nil, errors.New("value must be true or false") + } + return value, nil + case "text": + var value string + if err := json.Unmarshal(rawValue, &value); err != nil { + return nil, errors.New("value must be text") + } + if utf8.RuneCountInString(value) > *control.MaximumLength { + return nil, fmt.Errorf("value must contain at most %d characters", *control.MaximumLength) + } + return value, nil + default: + return nil, fmt.Errorf("unsupported control type %q", control.Type) + } +} + +// Close releases every discovered USB connection exactly once. It does not +// alter inventory or attempt to reconnect devices because shutdown/restart is +// the lifecycle boundary chosen for this feature. +func (manager *PeripheralManager) Close() { + if manager == nil { + return + } + manager.closeOnce.Do(func() { + manager.cancel() + manager.mu.Lock() + defer manager.mu.Unlock() + for _, peripheral := range manager.peripherals { + if err := peripheral.connection.Close(); err != nil { + manager.logger.Printf("close rover peripheral %s on %s: %v", peripheral.metadata.ID, peripheral.devicePath, err) + } + } + }) +} diff --git a/pi/roverd/peripheral_manager_test.go b/pi/roverd/peripheral_manager_test.go new file mode 100644 index 00000000..c262b647 --- /dev/null +++ b/pi/roverd/peripheral_manager_test.go @@ -0,0 +1,425 @@ +package roverd + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "log" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestPeripheralManagerDiscoversInventoryAndDispatchesControls(t *testing.T) { + description := testPeripheralDescription("Bench accessory", false) + connection := scriptedPeripheralConnection(t, description) + dependencies := testPeripheralDiscoveryDependencies( + []string{"/dev/ttyUSB9"}, + map[string]*scriptedConnection{"/dev/ttyUSB9": connection}, + ) + + manager, err := discoverPeripheralManager(context.Background(), "/dev/ttyUSB0", discardLogger(), dependencies) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + + inventory := manager.Inventory() + if len(inventory) != 1 { + t.Fatalf("inventory length = %d, want 1", len(inventory)) + } + if inventory[0].ID != "firmata-0" || inventory[0].Name != "Bench accessory" { + t.Fatalf("unexpected peripheral metadata: %#v", inventory[0]) + } + wantBroadcast := `Rover peripheral "Bench accessory" connected as firmata-0 with 3 additional controls.` + if broadcasts := manager.StartupBroadcasts(); len(broadcasts) != 1 || broadcasts[0] != wantBroadcast { + t.Fatalf("startup broadcasts = %#v, want %q", broadcasts, wantBroadcast) + } + wantOrder := []string{"servoPosition", "lightBrightness", "specialAction"} + for index, controlID := range wantOrder { + if inventory[0].Controls[index].ID != controlID { + t.Fatalf("control %d = %q, want %q", index, inventory[0].Controls[index].ID, controlID) + } + } + *inventory[0].Controls[0].Minimum = 99 + if fresh := manager.Inventory(); *fresh[0].Controls[0].Minimum != 0 { + t.Fatal("caller mutation changed the manager's fixed inventory") + } + + // Standard modes are configured once during discovery. Runtime slider + // commands should consequently contain only EXTENDED_ANALOG, not repeated + // mode changes that would detach and reattach a servo while it is moving. + baseline := len(connection.Bytes()) + if err := manager.SetControl("firmata-0", "servoPosition", json.RawMessage(`90`)); err != nil { + t.Fatalf("set servo: %v", err) + } + servoWrite := connection.Bytes()[baseline:] + wantServo := []byte{firmataStartSysex, firmataExtendedAnalog, 13, 90, firmataEndSysex} + if !bytes.Equal(servoWrite, wantServo) { + t.Fatalf("servo bytes = %v, want %v", servoWrite, wantServo) + } + + baseline = len(connection.Bytes()) + if err := manager.SetControl("firmata-0", "lightBrightness", json.RawMessage(`128`)); err != nil { + t.Fatalf("set PWM: %v", err) + } + pwmWrite := connection.Bytes()[baseline:] + wantPWM := []byte{firmataStartSysex, firmataExtendedAnalog, 17, 0, 1, firmataEndSysex} + if !bytes.Equal(pwmWrite, wantPWM) { + t.Fatalf("PWM bytes = %v, want %v", pwmWrite, wantPWM) + } + + baseline = len(connection.Bytes()) + if err := manager.SetControl("firmata-0", "specialAction", json.RawMessage(`true`)); err != nil { + t.Fatalf("set custom button: %v", err) + } + customWrite := connection.Bytes()[baseline:] + if len(customWrite) < 5 || customWrite[1] != firmataPeripheralFeature || customWrite[2] != firmataPeripheralControl { + t.Fatalf("custom control did not use rover-peripheral SysEx: %v", customWrite) + } +} + +func TestPeripheralManagerBroadcastsNoDevices(t *testing.T) { + manager := &PeripheralManager{byID: make(map[string]*managedPeripheral)} + want := "No ESP32 rover peripherals detected during startup." + if messages := manager.StartupBroadcasts(); len(messages) != 1 || messages[0] != want { + t.Fatalf("startup broadcasts = %#v, want %q", messages, want) + } +} + +func TestPeripheralManagerReportsUnexpectedDisconnectOnce(t *testing.T) { + connection := scriptedPeripheralConnection(t, testPeripheralDescription("Bench accessory", false)) + manager, err := discoverPeripheralManager( + context.Background(), + "/dev/roomba", + discardLogger(), + testPeripheralDiscoveryDependencies([]string{"/dev/accessory"}, map[string]*scriptedConnection{"/dev/accessory": connection}), + ) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + + // Closing the fake read stream models an unplugged USB serial adapter. The + // manager should publish one identified failure and never attempt reconnect. + _ = connection.Close() + select { + case failure := <-manager.Failures(): + if failure.ID != "firmata-0" || failure.Name != "Bench accessory" || !errors.Is(failure.Err, io.ErrClosedPipe) { + t.Fatalf("unexpected failure: %#v", failure) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for peripheral disconnect") + } + select { + case duplicate := <-manager.Failures(): + t.Fatalf("unexpected duplicate disconnect: %#v", duplicate) + case <-time.After(20 * time.Millisecond): + } +} + +func TestPeripheralManagerRejectsInvalidValuesBeforeWriting(t *testing.T) { + connection := scriptedPeripheralConnection(t, testPeripheralDescription("Bench accessory", false)) + manager, err := discoverPeripheralManager( + context.Background(), + "/dev/roomba", + discardLogger(), + testPeripheralDiscoveryDependencies([]string{"/dev/accessory"}, map[string]*scriptedConnection{"/dev/accessory": connection}), + ) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + + baseline := len(connection.Bytes()) + invalid := []struct { + control string + value string + }{ + {control: "servoPosition", value: `181`}, + {control: "lightBrightness", value: `12.5`}, + {control: "specialAction", value: `"yes"`}, + } + for _, testCase := range invalid { + if err := manager.SetControl("firmata-0", testCase.control, json.RawMessage(testCase.value)); err == nil { + t.Fatalf("expected %s=%s to fail", testCase.control, testCase.value) + } + } + if got := len(connection.Bytes()); got != baseline { + t.Fatalf("invalid values wrote %d bytes", got-baseline) + } +} + +func TestPeripheralManagerSkipsOtherFirmataFirmware(t *testing.T) { + other := newScriptedConnection(testFirmwareFrame("StandardFirmata")) + other.timeoutsBeforeRead = 1 + rover := scriptedPeripheralConnection(t, testPeripheralDescription("Rover accessory", false)) + dependencies := testPeripheralDiscoveryDependencies( + []string{"/dev/ttyACM0", "/dev/ttyUSB0"}, + map[string]*scriptedConnection{ + "/dev/ttyACM0": other, + "/dev/ttyUSB0": rover, + }, + ) + + manager, err := discoverPeripheralManager(context.Background(), "/dev/roomba", discardLogger(), dependencies) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + if inventory := manager.Inventory(); len(inventory) != 1 || inventory[0].ID != "firmata-0" || inventory[0].Name != "Rover accessory" { + t.Fatalf("unexpected inventory: %#v", inventory) + } + if !other.Closed() { + t.Fatal("non-rover Firmata port was not closed") + } +} + +func TestPeripheralManagerFailsMalformedRoverDescription(t *testing.T) { + connection := newScriptedConnection( + testFirmwareFrame(peripheralFirmwareName), + testCapabilityFrame(), + testDescriptionFrame([]byte(`not-json`)), + ) + connection.timeoutsBeforeRead = 1 + dependencies := testPeripheralDiscoveryDependencies( + []string{"/dev/ttyUSB0"}, + map[string]*scriptedConnection{"/dev/ttyUSB0": connection}, + ) + + manager, err := discoverPeripheralManager(context.Background(), "/dev/roomba", discardLogger(), dependencies) + if err == nil || !strings.Contains(err.Error(), "describe rover peripheral") { + t.Fatalf("expected malformed description error, got manager=%v err=%v", manager, err) + } + if !connection.Closed() { + t.Fatal("malformed rover peripheral connection was not closed") + } +} + +func TestPeripheralManagerRejectsAdvertisedUnsupportedPinMode(t *testing.T) { + description := testPeripheralDescription("Bad capability", false) + rawDescription, err := json.Marshal(description) + if err != nil { + t.Fatalf("marshal description: %v", err) + } + connection := newScriptedConnection( + testFirmwareFrame(peripheralFirmwareName), + []byte{ + firmataStartSysex, firmataCapabilityReply, + FirmataPinModeOutput, 1, 0x7F, + firmataEndSysex, + }, + testDescriptionFrame(rawDescription), + ) + connection.timeoutsBeforeRead = 1 + dependencies := testPeripheralDiscoveryDependencies( + []string{"/dev/ttyUSB0"}, + map[string]*scriptedConnection{"/dev/ttyUSB0": connection}, + ) + + manager, err := discoverPeripheralManager(context.Background(), "/dev/roomba", discardLogger(), dependencies) + if err == nil || !strings.Contains(err.Error(), "Firmata reported only 1 pins") { + t.Fatalf("expected unsupported capability error, got manager=%v err=%v", manager, err) + } + if !connection.Closed() { + t.Fatal("unsupported peripheral connection was not closed") + } +} + +func TestPeripheralManagerRejectsDuplicateBuiltInProvidersWhenRoleIsSelected(t *testing.T) { + first := scriptedPeripheralConnection(t, testPeripheralDescription("First", true)) + second := scriptedPeripheralConnection(t, testPeripheralDescription("Second", true)) + dependencies := testPeripheralDiscoveryDependencies( + []string{"/dev/ttyUSB0", "/dev/ttyUSB1"}, + map[string]*scriptedConnection{ + "/dev/ttyUSB0": first, + "/dev/ttyUSB1": second, + }, + ) + + manager, err := discoverPeripheralManager(context.Background(), "/dev/roomba", discardLogger(), dependencies) + if err != nil { + t.Fatalf("discovery should retain providers until native precedence is known: %v", err) + } + defer manager.Close() + if _, err := manager.NewFirmataToggle("headlight", discardLogger()); err == nil || !strings.Contains(err.Error(), "role headlight has multiple providers") { + t.Fatalf("expected duplicate provider selection error, got %v", err) + } + if first.Closed() || second.Closed() { + t.Fatal("selection validation unexpectedly closed manager-owned ports") + } +} + +func TestPeripheralManagerReturnsHardwareWriteFailure(t *testing.T) { + connection := scriptedPeripheralConnection(t, testPeripheralDescription("Bench accessory", false)) + manager, err := discoverPeripheralManager( + context.Background(), + "/dev/roomba", + discardLogger(), + testPeripheralDiscoveryDependencies([]string{"/dev/accessory"}, map[string]*scriptedConnection{"/dev/accessory": connection}), + ) + if err != nil { + t.Fatalf("discover: %v", err) + } + defer manager.Close() + + connection.SetWriteError(errors.New("USB device removed")) + err = manager.SetControl("firmata-0", "lightBrightness", json.RawMessage(`128`)) + if err == nil || !strings.Contains(err.Error(), "USB device removed") { + t.Fatalf("expected hardware error, got %v", err) + } + select { + case failure := <-manager.Failures(): + if failure.ID != "firmata-0" || !strings.Contains(failure.Err.Error(), "USB device removed") { + t.Fatalf("unexpected write failure notification: %#v", failure) + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for write failure notification") + } +} + +func TestPeripheralManagerPassesRoombaDeviceToCandidateExclusion(t *testing.T) { + const roombaDevice = "/dev/serial/by-id/roomba-base" + listed := false + dependencies := peripheralDiscoveryDependencies{ + listCandidates: func(excluded string) ([]string, error) { + listed = true + if excluded != roombaDevice { + t.Fatalf("excluded device = %q, want %q", excluded, roombaDevice) + } + return nil, nil + }, + open: func(string) (io.ReadWriteCloser, error) { return nil, errors.New("unexpected open") }, + sleep: func(time.Duration) {}, + startupWait: 0, + handshakeWait: time.Second, + } + + manager, err := discoverPeripheralManager(context.Background(), roombaDevice, discardLogger(), dependencies) + if err != nil { + t.Fatalf("discover: %v", err) + } + manager.Close() + if !listed { + t.Fatal("candidate listing was not called") + } +} + +func TestUniquePeripheralCandidatesPrefersStableAliasAndExcludesRoomba(t *testing.T) { + temporaryDirectory := t.TempDir() + peripheralTarget := filepath.Join(temporaryDirectory, "ttyUSB0") + roombaTarget := filepath.Join(temporaryDirectory, "ttyUSB1") + if err := os.WriteFile(peripheralTarget, nil, 0o600); err != nil { + t.Fatalf("create peripheral target: %v", err) + } + if err := os.WriteFile(roombaTarget, nil, 0o600); err != nil { + t.Fatalf("create Roomba target: %v", err) + } + stableAlias := filepath.Join(temporaryDirectory, "usb-rover-peripheral") + if err := os.Symlink(peripheralTarget, stableAlias); err != nil { + t.Fatalf("create stable alias: %v", err) + } + + candidates := uniquePeripheralCandidates( + []string{stableAlias, peripheralTarget, roombaTarget}, + roombaTarget, + ) + if len(candidates) != 1 || candidates[0] != stableAlias { + t.Fatalf("candidates = %v, want stable peripheral alias only", candidates) + } +} + +func testPeripheralDiscoveryDependencies(paths []string, connections map[string]*scriptedConnection) peripheralDiscoveryDependencies { + return peripheralDiscoveryDependencies{ + listCandidates: func(string) ([]string, error) { + return append([]string(nil), paths...), nil + }, + open: func(devicePath string) (io.ReadWriteCloser, error) { + connection := connections[devicePath] + if connection == nil { + return nil, errors.New("test connection not found") + } + return connection, nil + }, + sleep: func(time.Duration) {}, + startupWait: 0, + handshakeWait: time.Second, + } +} + +func scriptedPeripheralConnection(t *testing.T, description PeripheralDescription) *scriptedConnection { + t.Helper() + rawDescription, err := json.Marshal(description) + if err != nil { + t.Fatalf("marshal description: %v", err) + } + connection := newScriptedConnection( + testFirmwareFrame(peripheralFirmwareName), + testCapabilityFrame(), + testDescriptionFrame(rawDescription), + ) + // The first read represents the quiet timeout used to drain boot output + // before the client's parser starts consuming explicit query responses. + connection.timeoutsBeforeRead = 1 + return connection +} + +func testPeripheralDescription(name string, provideHeadlight bool) PeripheralDescription { + minimumServo, maximumServo := 0, 180 + minimumPWM, maximumPWM := 0, 255 + servoPin, pwmPin := 13, 17 + description := PeripheralDescription{ + Name: name, + Controls: []PeripheralControl{ + { + ID: "servoPosition", Type: "slider", Name: "Servo position", + Minimum: &minimumServo, Maximum: &maximumServo, + Output: PeripheralOutput{Type: "servo", Pin: &servoPin}, + }, + { + ID: "lightBrightness", Type: "slider", Name: "Light brightness", + Minimum: &minimumPWM, Maximum: &maximumPWM, + Output: PeripheralOutput{Type: "pwm", Pin: &pwmPin}, + }, + { + ID: "specialAction", Type: "button", Name: "Run special action", Mode: "momentary", + Output: PeripheralOutput{Type: "custom"}, + }, + }, + } + if provideHeadlight { + description.RoverControls.Headlight = &PeripheralDigitalRole{Pin: 18} + } + return description +} + +func testFirmwareFrame(name string) []byte { + frame := []byte{firmataStartSysex, firmataReportFirmware, 1, 0} + frame = append(frame, EncodeFirmata7Bit([]byte(name))...) + return append(frame, firmataEndSysex) +} + +func testCapabilityFrame() []byte { + frame := []byte{firmataStartSysex, firmataCapabilityReply} + for pin := 0; pin < 40; pin++ { + // The test ESP32 reports the same three output modes as the reference + // firmware. Repeating real pin entries also exercises capability parsing + // independently of any particular example control pin. + frame = append(frame, FirmataPinModeOutput, 1, FirmataPinModePWM, 8, FirmataPinModeServo, 14, 0x7F) + } + return append(frame, firmataEndSysex) +} + +func testDescriptionFrame(rawDescription []byte) []byte { + frame := []byte{firmataStartSysex, firmataPeripheralFeature, firmataPeripheralDescription} + frame = append(frame, EncodeFirmata7Bit(rawDescription)...) + return append(frame, firmataEndSysex) +} + +func discardLogger() *log.Logger { + return log.New(io.Discard, "", 0) +} diff --git a/pi/roverd/wsclient.go b/pi/roverd/wsclient.go index ebf92fee..6a82f7df 100644 --- a/pi/roverd/wsclient.go +++ b/pi/roverd/wsclient.go @@ -19,10 +19,11 @@ type WSClient struct { sensorFrames <-chan []byte events chan RoverEvent media *MediaSupervisor - servo *CameraServo + servo CameraServoController horn *HornSynth - headlight *GPIOToggle - laser *GPIOToggle + headlight ToggleController + laser ToggleController + peripherals *PeripheralManager log *log.Logger console *ConsoleNotifier recoverMu sync.Mutex @@ -45,7 +46,7 @@ type WSClient struct { audioMu sync.RWMutex } -func NewWSClient(cfg *Config, adapter *SerialAdapter, frames <-chan []byte, events chan RoverEvent, media *MediaSupervisor, servo *CameraServo, headlight *GPIOToggle, laser *GPIOToggle, logger *log.Logger, console *ConsoleNotifier) *WSClient { +func NewWSClient(cfg *Config, adapter *SerialAdapter, frames <-chan []byte, events chan RoverEvent, media *MediaSupervisor, servo CameraServoController, headlight ToggleController, laser ToggleController, peripherals *PeripheralManager, logger *log.Logger, console *ConsoleNotifier) *WSClient { var ttsQueue chan *ttsPayload if cfg.Audio.TTSEnabled { ttsQueue = make(chan *ttsPayload, 2) @@ -68,6 +69,7 @@ func NewWSClient(cfg *Config, adapter *SerialAdapter, frames <-chan []byte, even horn: horn, headlight: headlight, laser: laser, + peripherals: peripherals, log: logger, console: console, ttsQueue: ttsQueue, @@ -129,6 +131,20 @@ func (c *WSClient) Run(ctx context.Context) error { } func (c *WSClient) sendHello(ctx context.Context, conn *websocket.Conn) error { + // Built-in metadata comes from the selected controller, not necessarily + // YAML. An ESP32 can enable a role whose native GPIO entry is disabled. + cameraServoConfig := CameraServoConfig{} + if c.servo != nil { + cameraServoConfig = c.servo.Configuration() + } + headlightConfig := GPIOToggleConfig{} + if c.headlight != nil { + headlightConfig = c.headlight.Configuration() + } + laserConfig := GPIOToggleConfig{} + if c.laser != nil { + laserConfig = c.laser.Configuration() + } msg := helloMessage{ Type: "hello", Name: c.cfg.Name, @@ -137,11 +153,12 @@ func (c *WSClient) sendHello(ctx context.Context, conn *websocket.Conn) error { Battery: c.cfg.Battery, MaxWheelSpeed: c.cfg.MaxWheelMMs, Media: c.cfg.Media, - CameraServo: c.cfg.CameraServo, + CameraServo: cameraServoConfig, Audio: c.cfg.Audio, Horn: c.cfg.Horn, - Headlight: c.cfg.Headlight, - Laser: c.cfg.Laser, + Headlight: headlightConfig, + Laser: laserConfig, + Peripherals: c.peripherals.Inventory(), Private: c.cfg.Private, } c.log.Printf("sending hello (camera servo enabled=%v pin=%d)", msg.CameraServo.Enabled, msg.CameraServo.Pin) @@ -238,6 +255,8 @@ func (c *WSClient) dispatch(ctx context.Context, msg *inboundMessage) error { return c.handleToggleCommand("headlight", c.headlight, msg.Headlight) case msg.Laser != nil: return c.handleToggleCommand("laser", c.laser, msg.Laser) + case msg.Peripheral != nil: + return c.peripherals.SetControl(msg.Peripheral.ID, msg.Peripheral.Control, msg.Peripheral.Value) case msg.Song != nil: slot := 0 if msg.Song.Slot != nil { @@ -253,7 +272,7 @@ func (c *WSClient) dispatch(ctx context.Context, msg *inboundMessage) error { } } -func (c *WSClient) handleToggleCommand(name string, toggle *GPIOToggle, payload *togglePayload) error { +func (c *WSClient) handleToggleCommand(name string, toggle ToggleController, payload *togglePayload) error { if toggle == nil { return fmt.Errorf("%s disabled", name) } diff --git a/server/public/assets/FleetReportsApp-DYMSv9Or.js b/server/public/assets/FleetReportsApp-ChDcFmTF.js similarity index 99% rename from server/public/assets/FleetReportsApp-DYMSv9Or.js rename to server/public/assets/FleetReportsApp-ChDcFmTF.js index 88581815..8ca7de26 100644 --- a/server/public/assets/FleetReportsApp-DYMSv9Or.js +++ b/server/public/assets/FleetReportsApp-ChDcFmTF.js @@ -1,4 +1,4 @@ -import{g as Me,u as Ae,a as Ne,i as De,b as Ie,D as Le,c as Te,j as e,S as Fe,t as Pe,C as de,L as Oe,r as he,d as We,e as Ue}from"./index-BZ2ymoHR.js";var be={exports:{}};var He=be.exports,Ce;function $e(){return Ce||(Ce=1,(function(S,M){(($,b)=>{S.exports=b()})(He,function $(){var b=typeof self<"u"?self:typeof window<"u"?window:b!==void 0?b:{},B,z=!b.document&&!!b.postMessage,f=b.IS_PAPA_WORKER||!1,ie={},w=0,o={};function j(t){return t.charCodeAt(0)===65279?t.slice(1):t}function C(t){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},(function(r){var s=je(r);s.chunkSize=parseInt(s.chunkSize),r.step||r.chunk||(s.chunkSize=null),this._handle=new pe(s),(this._handle.streamer=this)._config=s}).call(this,t),this.parseChunk=function(r,s){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview),f)b.postMessage({results:n,workerId:o.WORKER_ID,finished:i});else if(E(this._config.chunk)&&!s){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=n=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!i||!E(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(r){E(this._config.error)?this._config.error(r):f&&this._config.error&&b.postMessage({workerId:o.WORKER_ID,error:r,finished:!1})}}function re(t){var r;(t=t||{}).chunkSize||(t.chunkSize=o.RemoteChunkSize),C.call(this,t),this._nextChunk=z?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(s){this._input=s,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(r=new XMLHttpRequest,this._config.withCredentials&&(r.withCredentials=this._config.withCredentials),z||(r.onload=ae(this._chunkLoaded,this),r.onerror=ae(this._chunkError,this)),r.open(this._config.downloadRequestBody?"POST":"GET",this._input,!z),this._config.downloadRequestHeaders){var s,i=this._config.downloadRequestHeaders;for(s in i)r.setRequestHeader(s,i[s])}var n;this._config.chunkSize&&(n=this._start+this._config.chunkSize-1,r.setRequestHeader("Range","bytes="+this._start+"-"+n));try{r.send(this._config.downloadRequestBody)}catch(p){this._chunkError(p.message)}z&&r.status===0&&this._chunkError()}},this._chunkLoaded=function(){r.readyState===4&&(r.status<200||400<=r.status?this._chunkError():(this._start+=this._config.chunkSize||r.responseText.length,this._finished=!this._config.chunkSize||this._start>=(s=>(s=s.getResponseHeader("Content-Range"))!==null?parseInt(s.substring(s.lastIndexOf("/")+1)):-1)(r),this.parseChunk(r.responseText)))},this._chunkError=function(s){s=r.statusText||s,this._sendError(new Error(s))}}function O(t){(t=t||{}).chunkSize||(t.chunkSize=o.LocalChunkSize),C.call(this,t);var r,s,i=typeof FileReader<"u";this.stream=function(n){this._input=n,s=n.slice||n.webkitSlice||n.mozSlice,i?((r=new FileReader).onload=ae(this._chunkLoaded,this),r.onerror=ae(this._chunkError,this)):r=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(n.target.result)},this._chunkError=function(){this._sendError(r.error)}}function l(t){var r;C.call(this,t=t||{}),this.stream=function(s){return r=s,this._nextChunk()},this._nextChunk=function(){var s,i;if(!this._finished)return s=this._config.chunkSize,r=s?(i=r.substring(0,s),r.substring(s)):(i=r,""),this._finished=!r,this.parseChunk(i)}}function v(t){C.call(this,t=t||{});var r=[],s=!0,i=!1;this.pause=function(){C.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){C.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(n){this._input=n,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&r.length===1&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),r.length?this.parseChunk(r.shift()):s=!0},this._streamData=ae(function(n){try{r.push(typeof n=="string"?n:n.toString(this._config.encoding)),s&&(s=!1,this._checkIsFinished(),this.parseChunk(r.shift()))}catch(p){this._streamError(p)}},this),this._streamError=ae(function(n){this._streamCleanUp(),this._sendError(n)},this),this._streamEnd=ae(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=ae(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function pe(t){var r,s,i,n,p=Math.pow(2,53),L=-p,V=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,G=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,m=this,T=0,d=0,K=!1,c=!1,g=[],a={data:[],errors:[],meta:{}};function J(_){return t.skipEmptyLines==="greedy"?_.join("").trim()==="":_.length===1&&_[0].length===0}function A(){if(a&&i&&(Y("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+o.DefaultDelimiter+"'"),i=!1),t.skipEmptyLines&&(a.data=a.data.filter(function(h){return!J(h)})),P()){let h=function(D,U){D=j(D),E(t.transformHeader)&&(D=t.transformHeader(D,U)),g.push(D)};if(a)if(Array.isArray(a.data[0])){for(var _=0;P()&&_(I=>(t.dynamicTypingFunction&&t.dynamicTyping[I]===void 0&&(t.dynamicTyping[I]=t.dynamicTypingFunction(I)),(t.dynamicTyping[I]||t.dynamicTyping)===!0))(Z)?u==="true"||u==="TRUE"||u!=="false"&&u!=="FALSE"&&((I=>{if(V.test(I)&&(I=parseFloat(I),L=g.length?"__parsed_extra":g[R]:N,y=t.transform?t.transform(y,N):y);N==="__parsed_extra"?(U[N]=U[N]||[],U[N].push(y)):U[N]=y}return t.header&&(R>g.length?Y("FieldMismatch","TooManyFields","Too many fields: expected "+g.length+" fields but parsed "+R,d+D):Rt.preview?s.abort():(a.data=a.data[0],n(a,m))))}),this.parse=function(_,x,F){var h=t.quoteChar||'"',h=(t.newline||(t.newline=this.guessLineEndings(_,h)),i=!1,t.delimiter?E(t.delimiter)&&(t.delimiter=t.delimiter(_),a.meta.delimiter=t.delimiter):((h=((D,U,R,N,y)=>{var Z,u,I,le;y=y||[","," ","|",";",o.RECORD_SEP,o.UNIT_SEP];for(var ue=0;ue{S.exports=b()})(He,function $(){var b=typeof self<"u"?self:typeof window<"u"?window:b!==void 0?b:{},B,z=!b.document&&!!b.postMessage,f=b.IS_PAPA_WORKER||!1,ie={},w=0,o={};function j(t){return t.charCodeAt(0)===65279?t.slice(1):t}function C(t){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},(function(r){var s=je(r);s.chunkSize=parseInt(s.chunkSize),r.step||r.chunk||(s.chunkSize=null),this._handle=new pe(s),(this._handle.streamer=this)._config=s}).call(this,t),this.parseChunk=function(r,s){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview),f)b.postMessage({results:n,workerId:o.WORKER_ID,finished:i});else if(E(this._config.chunk)&&!s){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=n=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!i||!E(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(r){E(this._config.error)?this._config.error(r):f&&this._config.error&&b.postMessage({workerId:o.WORKER_ID,error:r,finished:!1})}}function re(t){var r;(t=t||{}).chunkSize||(t.chunkSize=o.RemoteChunkSize),C.call(this,t),this._nextChunk=z?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(s){this._input=s,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(r=new XMLHttpRequest,this._config.withCredentials&&(r.withCredentials=this._config.withCredentials),z||(r.onload=ae(this._chunkLoaded,this),r.onerror=ae(this._chunkError,this)),r.open(this._config.downloadRequestBody?"POST":"GET",this._input,!z),this._config.downloadRequestHeaders){var s,i=this._config.downloadRequestHeaders;for(s in i)r.setRequestHeader(s,i[s])}var n;this._config.chunkSize&&(n=this._start+this._config.chunkSize-1,r.setRequestHeader("Range","bytes="+this._start+"-"+n));try{r.send(this._config.downloadRequestBody)}catch(p){this._chunkError(p.message)}z&&r.status===0&&this._chunkError()}},this._chunkLoaded=function(){r.readyState===4&&(r.status<200||400<=r.status?this._chunkError():(this._start+=this._config.chunkSize||r.responseText.length,this._finished=!this._config.chunkSize||this._start>=(s=>(s=s.getResponseHeader("Content-Range"))!==null?parseInt(s.substring(s.lastIndexOf("/")+1)):-1)(r),this.parseChunk(r.responseText)))},this._chunkError=function(s){s=r.statusText||s,this._sendError(new Error(s))}}function O(t){(t=t||{}).chunkSize||(t.chunkSize=o.LocalChunkSize),C.call(this,t);var r,s,i=typeof FileReader<"u";this.stream=function(n){this._input=n,s=n.slice||n.webkitSlice||n.mozSlice,i?((r=new FileReader).onload=ae(this._chunkLoaded,this),r.onerror=ae(this._chunkError,this)):r=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(n.target.result)},this._chunkError=function(){this._sendError(r.error)}}function l(t){var r;C.call(this,t=t||{}),this.stream=function(s){return r=s,this._nextChunk()},this._nextChunk=function(){var s,i;if(!this._finished)return s=this._config.chunkSize,r=s?(i=r.substring(0,s),r.substring(s)):(i=r,""),this._finished=!r,this.parseChunk(i)}}function v(t){C.call(this,t=t||{});var r=[],s=!0,i=!1;this.pause=function(){C.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){C.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(n){this._input=n,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&r.length===1&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),r.length?this.parseChunk(r.shift()):s=!0},this._streamData=ae(function(n){try{r.push(typeof n=="string"?n:n.toString(this._config.encoding)),s&&(s=!1,this._checkIsFinished(),this.parseChunk(r.shift()))}catch(p){this._streamError(p)}},this),this._streamError=ae(function(n){this._streamCleanUp(),this._sendError(n)},this),this._streamEnd=ae(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=ae(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function pe(t){var r,s,i,n,p=Math.pow(2,53),L=-p,V=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,G=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,m=this,T=0,d=0,K=!1,c=!1,g=[],a={data:[],errors:[],meta:{}};function J(_){return t.skipEmptyLines==="greedy"?_.join("").trim()==="":_.length===1&&_[0].length===0}function A(){if(a&&i&&(Y("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+o.DefaultDelimiter+"'"),i=!1),t.skipEmptyLines&&(a.data=a.data.filter(function(h){return!J(h)})),P()){let h=function(D,U){D=j(D),E(t.transformHeader)&&(D=t.transformHeader(D,U)),g.push(D)};if(a)if(Array.isArray(a.data[0])){for(var _=0;P()&&_(I=>(t.dynamicTypingFunction&&t.dynamicTyping[I]===void 0&&(t.dynamicTyping[I]=t.dynamicTypingFunction(I)),(t.dynamicTyping[I]||t.dynamicTyping)===!0))(Z)?u==="true"||u==="TRUE"||u!=="false"&&u!=="FALSE"&&((I=>{if(V.test(I)&&(I=parseFloat(I),L=g.length?"__parsed_extra":g[R]:N,y=t.transform?t.transform(y,N):y);N==="__parsed_extra"?(U[N]=U[N]||[],U[N].push(y)):U[N]=y}return t.header&&(R>g.length?Y("FieldMismatch","TooManyFields","Too many fields: expected "+g.length+" fields but parsed "+R,d+D):Rt.preview?s.abort():(a.data=a.data[0],n(a,m))))}),this.parse=function(_,x,F){var h=t.quoteChar||'"',h=(t.newline||(t.newline=this.guessLineEndings(_,h)),i=!1,t.delimiter?E(t.delimiter)&&(t.delimiter=t.delimiter(_),a.meta.delimiter=t.delimiter):((h=((D,U,R,N,y)=>{var Z,u,I,le;y=y||[","," ","|",";",o.RECORD_SEP,o.UNIT_SEP];for(var ue=0;ue=F.length/2?`\r @@ -8,4 +8,4 @@ import{g as Me,u as Ae,a as Ne,i as De,b as Ie,D as Le,c as Te,j as e,S as Fe,t `);var d=0,K=!1;this.parse=function(c,g,a){if(typeof c!="string")throw new Error("Input must be a string");var J=c.length,A=r.length,P=s.length,Y=i.length,_=E(n),x=[],F=[],h=[],D=d=0;if(!c)return q();if(L||L!==!1&&c.indexOf(m)===-1){for(var U=c.split(s),R=0;R=p)return q(!0);break}F.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:x.length,index:d}),u++}}else if(i&&h.length===0&&c.substring(d,d+Y)===i){if(y===-1)return q();d=y+P,y=c.indexOf(s,d),N=c.indexOf(r,d)}else if(N!==-1&&(N=p)return q(!0)}return X();function le(H){x.push(H),D=d}function ue(H){var Q=0;return Q=H!==-1&&(H=c.substring(u+1,H))&&H.trim()===""?H.length:Q}function X(H){return a||(H===void 0&&(H=c.substring(d)),h.push(H),d=J,le(h),_&&ee()),q()}function me(H){d=H,le(h),h=[],y=c.indexOf(s,d)}function q(H){if(t.header&&!g&&x.length&&!G){var Q=x[0],ne=Object.create(null),_e=new Set(Q);let we=!1;for(let fe=0;fe{var i;return!!o.WORKERS_SUPPORTED&&(i=(()=>{var n=b.URL||b.webkitURL||null,p=$.toString();return o.BLOB_URL||(o.BLOB_URL=n.createObjectURL(new Blob(["var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; ","(",p,")();"],{type:"text/javascript"})))})(),(i=new b.Worker(i)).onmessage=ye,i.id=w++,ie[i.id]=i)})()).userStep=r.step,s.userChunk=r.chunk,s.userComplete=r.complete,s.userError=r.error,r.step=E(r.step),r.chunk=E(r.chunk),r.complete=E(r.complete),r.error=E(r.error),delete r.worker,s.postMessage({input:t,config:r,workerId:s.id})},o.unparse=function(t,r){var s=!1,i=!0,n=",",p=`\r `,L='"',V=L+L,G=!1,m=null,T=!1,d=((()=>{if(typeof r=="object"){if(typeof r.delimiter!="string"||o.BAD_DELIMITERS.filter(function(g){return r.delimiter.indexOf(g)!==-1}).length||(n=r.delimiter),typeof r.quotes!="boolean"&&typeof r.quotes!="function"&&!Array.isArray(r.quotes)||(s=r.quotes),typeof r.skipEmptyLines!="boolean"&&typeof r.skipEmptyLines!="string"||(G=r.skipEmptyLines),typeof r.newline=="string"&&(p=r.newline),typeof r.quoteChar=="string"&&(L=r.quoteChar,V=L+L),typeof r.header=="boolean"&&(i=r.header),Array.isArray(r.columns)){if(r.columns.length===0)throw new Error("Option columns is empty");m=r.columns}r.escapeChar!==void 0&&(V=r.escapeChar+L),r.escapeFormulae instanceof RegExp?T=r.escapeFormulae:typeof r.escapeFormulae=="boolean"&&r.escapeFormulae&&(T=/^[=+\-@\t\r].*$/)}})(),new RegExp(se(L),"g"));if(typeof t=="string"&&(t=JSON.parse(t)),Array.isArray(t)){if(!t.length||Array.isArray(t[0]))return K(null,t,G);if(typeof t[0]=="object")return K(m||Object.keys(t[0]),t,G)}else if(typeof t=="object")return typeof t.data=="string"&&(t.data=JSON.parse(t.data)),Array.isArray(t.data)&&(t.fields||(t.fields=t.meta&&t.meta.fields||m),t.fields||(t.fields=Array.isArray(t.data[0])?t.fields:typeof t.data[0]=="object"?Object.keys(t.data[0]):[]),Array.isArray(t.data[0])||typeof t.data[0]=="object"||(t.data=[t.data])),K(t.fields||[],t.data||[],G);throw new Error("Unable to serialize unrecognized input");function K(g,a,J){var A="",P=(typeof g=="string"&&(g=JSON.parse(g)),typeof a=="string"&&(a=JSON.parse(a)),Array.isArray(g)&&0{for(var x=0;x<_.length;x++)if(-1=1e6?`${k(M/1e6)} km`:`${k(M/1e3,1)} m`}function Be(S){const M=Ke.unparse(S.map(B=>({...B,batteryHealth:JSON.stringify(B.batteryHealth)}))),$=URL.createObjectURL(new Blob([M],{type:"text/csv;charset=utf-8"})),b=document.createElement("a");b.href=$,b.download="fleet-battery-efficiency.csv",b.click(),URL.revokeObjectURL($)}function oe({label:S,value:M,detail:$}){return e.jsxs("div",{className:"surface min-w-0 px-1 py-0.5",children:[e.jsx("div",{className:"text-[0.68rem] text-slate-400",children:S}),e.jsx("div",{className:"truncate text-sm font-semibold text-slate-100",children:M}),$?e.jsx("div",{className:"truncate text-[0.68rem] text-slate-500",children:$}):null]})}function W({field:S,sort:M,onSort:$,children:b}){return e.jsx("th",{children:e.jsxs("button",{type:"button",className:"whitespace-nowrap text-left",onClick:()=>$(S),children:[b,M.key===S?M.descending?" ↓":" ↑":""]})})}function Qe({report:S,refresh:M}){const $=Ue(),b=Ne(j=>j.session?.role||null),B=b==="admin"||b==="lockdown",[z,f]=he.useState({roverId:S.rovers[0]?.roverId||"",chemistry:"unknown",ratedCapacityMah:"",installedDate:new Date().toISOString().slice(0,10),notes:""}),[ie,w]=he.useState("");function o(j){j.preventDefault(),w("Saving…"),$.emit("fleetReports:replaceBattery",{...z,ratedCapacityMah:Number(z.ratedCapacityMah),installedAt:new Date(`${z.installedDate}T12:00:00`).getTime()},(C={})=>{w(C.error||`Registered ${C.battery?.batteryKey||"battery"}`),C.error||M()})}return e.jsxs(de,{title:"Battery identity and baselines",meta:`${S.batteryRegistry.length} records`,bodyClassName:"space-y-0.5 p-0.5",children:[e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full whitespace-nowrap text-left text-xs",children:[e.jsx("thead",{className:"text-slate-400",children:e.jsxs("tr",{children:[e.jsx("th",{children:"Rover"}),e.jsx("th",{children:"Battery"}),e.jsx("th",{children:"Chemistry"}),e.jsx("th",{children:"Rated mAh"}),e.jsx("th",{children:"Installed"}),e.jsx("th",{children:"Retired"}),e.jsx("th",{children:"Learned baseline mAh"}),e.jsx("th",{children:"Notes"})]})}),e.jsx("tbody",{children:S.batteryRegistry.map(j=>e.jsxs("tr",{className:"border-t border-neutral-700/70 text-slate-200",children:[e.jsx("td",{children:j.roverId}),e.jsx("td",{children:j.batteryKey}),e.jsx("td",{children:j.chemistry||"--"}),e.jsx("td",{children:k(j.ratedCapacityMah,0)}),e.jsx("td",{children:xe(j.installedAt)}),e.jsx("td",{children:xe(j.retiredAt)}),e.jsx("td",{children:k(j.healthyBaselineMah,0)}),e.jsx("td",{children:j.notes||"--"})]},j.batteryKey))})]})}),B?e.jsxs("form",{className:"surface grid gap-0.5 p-1 text-xs md:grid-cols-2 xl:grid-cols-6",onSubmit:o,children:[e.jsxs("label",{children:[e.jsx("span",{className:"text-slate-400",children:"Rover"}),e.jsx("select",{className:"w-full bg-neutral-800 p-0.5",value:z.roverId,onChange:j=>f(C=>({...C,roverId:j.target.value})),children:S.rovers.map(j=>e.jsx("option",{value:j.roverId,children:j.name},j.roverId))})]}),e.jsxs("label",{children:[e.jsx("span",{className:"text-slate-400",children:"Chemistry"}),e.jsxs("select",{className:"w-full bg-neutral-800 p-0.5",value:z.chemistry,onChange:j=>f(C=>({...C,chemistry:j.target.value})),children:[e.jsx("option",{value:"unknown",children:"Unknown"}),e.jsx("option",{value:"NiMH",children:"NiMH"}),e.jsx("option",{value:"Li-ion",children:"Li-ion"})]})]}),e.jsxs("label",{children:[e.jsx("span",{className:"text-slate-400",children:"Rated capacity mAh"}),e.jsx("input",{required:!0,min:"1",max:"65535",type:"number",className:"w-full bg-neutral-800 p-0.5",value:z.ratedCapacityMah,onChange:j=>f(C=>({...C,ratedCapacityMah:j.target.value}))})]}),e.jsxs("label",{children:[e.jsx("span",{className:"text-slate-400",children:"Installed"}),e.jsx("input",{required:!0,type:"date",className:"w-full bg-neutral-800 p-0.5",value:z.installedDate,onChange:j=>f(C=>({...C,installedDate:j.target.value}))})]}),e.jsxs("label",{children:[e.jsx("span",{className:"text-slate-400",children:"Notes"}),e.jsx("input",{className:"w-full bg-neutral-800 p-0.5",value:z.notes,onChange:j=>f(C=>({...C,notes:j.target.value}))})]}),e.jsxs("div",{className:"flex items-end gap-0.5",children:[e.jsx("button",{type:"submit",className:"button-dark px-1 py-0.5",children:"Install or replace"}),e.jsx("span",{className:"text-slate-400",children:ie})]})]}):null]})}function Ve(){const[S,M]=he.useState(Ee[1].ms),[$,b]=he.useState(()=>Date.now()),[B,z]=he.useState(""),[f,ie]=he.useState({key:"name",descending:!1}),{report:w,loading:o,error:j,refresh:C}=We({since:$-S,until:$,includeEvents:!1}),re=he.useMemo(()=>[...(w?.rovers||[]).filter(v=>`${v.name} ${v.roverId}`.toLowerCase().includes(B.trim().toLowerCase()))].sort((v,pe)=>{const se=f.key.startsWith("batteryHealth.")?v.batteryHealth?.[f.key.slice(14)]:v[f.key],ce=f.key.startsWith("batteryHealth.")?pe.batteryHealth?.[f.key.slice(14)]:pe[f.key],ye=typeof se=="string"?se.localeCompare(String(ce??"")):(Number(se)||0)-(Number(ce)||0);return f.descending?-ye:ye}),[B,w,f]);function O(l){ie(v=>({key:l,descending:v.key===l?!v.descending:!1}))}return e.jsxs(e.Fragment,{children:[e.jsxs(de,{title:"Fleet battery health and efficiency",meta:w?xe(w.generatedAt):"Loading",bodyClassName:"space-y-0.5 p-0.5",children:[e.jsxs("div",{className:"surface flex flex-wrap items-end gap-0.5 p-0.5 text-xs",children:[e.jsxs("label",{children:[e.jsx("span",{className:"mr-0.5 text-slate-400",children:"Range"}),e.jsx("select",{className:"bg-neutral-800 p-0.5",value:S,onChange:l=>M(Number(l.target.value)),children:Ee.map(l=>e.jsx("option",{value:l.ms,children:l.label},l.ms))})]}),e.jsxs("label",{children:[e.jsx("span",{className:"mr-0.5 text-slate-400",children:"Find rover"}),e.jsx("input",{className:"bg-neutral-800 p-0.5",value:B,onChange:l=>z(l.target.value)})]}),e.jsx("button",{type:"button",className:"button-dark px-1 py-0.5",onClick:()=>{b(Date.now()),C()},children:"Refresh"}),e.jsx(Oe,{className:"button-dark px-1 py-0.5",to:"/",children:"Back"})]}),o&&!w?e.jsx("p",{className:"text-slate-400",children:"Loading fleet metrics…"}):null,j?e.jsx("p",{className:"text-red-300",children:j}):null,w?e.jsxs("div",{className:"grid grid-cols-2 gap-0.5 md:grid-cols-4 xl:grid-cols-8",children:[e.jsx(oe,{label:"Rovers online",value:`${w.totals.onlineRoverCount}/${w.totals.roverCount}`}),e.jsx(oe,{label:"Distance",value:ve(w.totals.distanceMm)}),e.jsx(oe,{label:"Energy used",value:`${k(w.totals.dischargedWh)} Wh`}),e.jsx(oe,{label:"Fleet Wh/km",value:w.totals.overallWhPerKm==null?"--":k(w.totals.overallWhPerKm)}),e.jsx(oe,{label:"Moving Wh/km",value:w.totals.movingWhPerKm==null?"--":k(w.totals.movingWhPerKm)}),e.jsx(oe,{label:"Moving energy",value:`${k(w.totals.movingDischargedWh)} Wh`}),e.jsx(oe,{label:"Stationary energy",value:`${k(w.totals.stationaryDischargedWh)} Wh`}),e.jsx(oe,{label:"Attention",value:k(w.totals.attentionCount,0)})]}):null]}),w?e.jsxs(e.Fragment,{children:[e.jsx(de,{title:"All rovers",meta:`${re.length} visible`,actions:e.jsx("button",{type:"button",className:"button-dark px-1 py-0.25 text-xs",onClick:()=>Be(re),children:"Export CSV"}),bodyClassName:"overflow-x-auto p-0.5",children:e.jsxs("table",{className:"w-full whitespace-nowrap text-left text-xs",children:[e.jsx("thead",{className:"sticky top-0 bg-neutral-900 text-slate-400",children:e.jsxs("tr",{children:[e.jsx(W,{field:"name",sort:f,onSort:O,children:"Rover"}),e.jsx(W,{field:"online",sort:f,onSort:O,children:"State"}),e.jsx(W,{field:"batteryHealth.capacityRetentionPercent",sort:f,onSort:O,children:"Health"}),e.jsx(W,{field:"batteryHealth.measuredUsableMah",sort:f,onSort:O,children:"Usable Ah"}),e.jsx(W,{field:"batteryHealth.measuredUsableWh",sort:f,onSort:O,children:"Usable Wh"}),e.jsx(W,{field:"batteryHealth.confidence",sort:f,onSort:O,children:"Confidence"}),e.jsx(W,{field:"overallWhPerKm",sort:f,onSort:O,children:"Wh/km"}),e.jsx(W,{field:"movingWhPerKm",sort:f,onSort:O,children:"Moving Wh/km"}),e.jsx(W,{field:"distanceMm",sort:f,onSort:O,children:"Distance"}),e.jsx(W,{field:"dischargedWh",sort:f,onSort:O,children:"Used Wh"}),e.jsx(W,{field:"movingDischargedWh",sort:f,onSort:O,children:"Moving Wh"}),e.jsx(W,{field:"stationaryDischargedWh",sort:f,onSort:O,children:"Stationary Wh"}),e.jsx(W,{field:"movingMs",sort:f,onSort:O,children:"Moving time"}),e.jsx(W,{field:"averageSpeedMmPerSecond",sort:f,onSort:O,children:"Average speed"}),e.jsx(W,{field:"maximumSpeedMmPerSecond",sort:f,onSort:O,children:"Maximum speed"}),e.jsx(W,{field:"latestChargeMah",sort:f,onSort:O,children:"Charge"}),e.jsx(W,{field:"averageVoltageMv",sort:f,onSort:O,children:"Voltage"}),e.jsx(W,{field:"averageCurrentMa",sort:f,onSort:O,children:"Current"}),e.jsx(W,{field:"maximumTemperatureC",sort:f,onSort:O,children:"Temperature"}),e.jsx(W,{field:"lastSampleAt",sort:f,onSort:O,children:"Last data"})]})}),e.jsx("tbody",{children:re.map(l=>{const v=l.batteryHealth;return e.jsxs("tr",{className:"border-t border-neutral-700/70 text-slate-200",children:[e.jsx("td",{className:"sticky left-0 bg-neutral-900 font-semibold",children:l.name}),e.jsx("td",{children:l.online?"online":"offline"}),e.jsx("td",{children:v.capacityRetentionPercent==null?"--":`${k(v.capacityRetentionPercent,1)}%`}),e.jsx("td",{children:v.measuredUsableMah==null?"--":k(v.measuredUsableMah/1e3,3)}),e.jsx("td",{children:k(v.measuredUsableWh)}),e.jsx("td",{title:v.confidenceReason,children:v.confidence}),e.jsx("td",{children:l.overallWhPerKm==null?`need ${ve(l.efficiencyDistanceRequiredMm)}`:k(l.overallWhPerKm)}),e.jsx("td",{children:k(l.movingWhPerKm)}),e.jsx("td",{children:ve(l.distanceMm)}),e.jsx("td",{children:k(l.dischargedWh)}),e.jsx("td",{children:k(l.movingDischargedWh)}),e.jsx("td",{children:k(l.stationaryDischargedWh)}),e.jsx("td",{children:qe(l.movingMs)}),e.jsx("td",{children:l.averageSpeedMmPerSecond==null?"--":`${k(l.averageSpeedMmPerSecond)} mm/s`}),e.jsx("td",{children:l.maximumSpeedMmPerSecond==null?"--":`${k(l.maximumSpeedMmPerSecond)} mm/s`}),e.jsxs("td",{children:[k(l.latestChargeMah,0)," mAh"]}),e.jsxs("td",{children:[k(l.averageVoltageMv,0)," mV"]}),e.jsxs("td",{children:[k(l.averageCurrentMa,0)," mA"]}),e.jsxs("td",{children:[k(l.maximumTemperatureC,1)," °C"]}),e.jsx("td",{children:xe(l.lastSampleAt)})]},l.roverId)})})]})}),e.jsx(de,{title:"Battery-health evidence",meta:"All rovers",bodyClassName:"overflow-x-auto p-0.5",children:e.jsxs("table",{className:"w-full whitespace-nowrap text-left text-xs",children:[e.jsx("thead",{className:"text-slate-400",children:e.jsxs("tr",{children:[e.jsx("th",{children:"Rover"}),e.jsx("th",{children:"Battery"}),e.jsx("th",{children:"Reference mAh"}),e.jsx("th",{children:"Estimated usable mAh"}),e.jsx("th",{children:"Observed range floor"}),e.jsx("th",{children:"Retention"}),e.jsx("th",{children:"Observations"}),e.jsx("th",{children:"Average depth"}),e.jsx("th",{children:"Throughput"}),e.jsx("th",{children:"Latest evidence"}),e.jsx("th",{children:"Confidence basis"})]})}),e.jsx("tbody",{children:re.map(l=>{const v=l.batteryHealth;return e.jsxs("tr",{className:"border-t border-neutral-700/70 text-slate-200",children:[e.jsx("td",{children:l.name}),e.jsx("td",{children:v.batteryKey}),e.jsx("td",{children:k(v.referenceMah,0)}),e.jsx("td",{children:k(v.measuredUsableMah,0)}),e.jsx("td",{children:k(v.observedUsableFloorMah,0)}),e.jsx("td",{children:v.capacityRetentionPercent==null?"--":`${k(v.capacityRetentionPercent,1)}%`}),e.jsx("td",{children:k(v.observationCount,0)}),e.jsxs("td",{children:[k(v.averageObservationDepthPercent,1),"%"]}),e.jsxs("td",{children:[k(v.dischargedThroughputMah,0)," mAh"]}),e.jsx("td",{children:xe(v.latestObservationAt)}),e.jsx("td",{children:v.confidenceReason})]},l.roverId)})})]})}),w.attention.length?e.jsx(de,{title:"Attention",meta:w.attention.length,bodyClassName:"p-0.5 text-xs",children:e.jsxs("table",{className:"w-full text-left",children:[e.jsx("thead",{className:"text-slate-400",children:e.jsxs("tr",{children:[e.jsx("th",{children:"Rover"}),e.jsx("th",{children:"Severity"}),e.jsx("th",{children:"Reason"})]})}),e.jsx("tbody",{children:w.attention.map(l=>e.jsxs("tr",{className:"border-t border-neutral-700/70",children:[e.jsx("td",{children:l.roverId}),e.jsx("td",{children:l.severity}),e.jsx("td",{children:l.title})]},l.key))})]})}):null,e.jsx(Qe,{report:w,refresh:C}),e.jsxs(de,{title:"Methodology",bodyClassName:"space-y-0.5 p-1 text-xs text-slate-300",children:[e.jsx("p",{children:"Energy is integrated per sensor interval from OI voltage and signed current. Existing odometer distance and speed are consumed directly; reporting does not calculate a competing speed value."}),e.jsx("p",{children:"Capacity combines partial discharge current with packet 25 charge movement. Every usable observation contributes; deeper observations increase confidence without labeling cycles good or bad. Packet 26 is only a fixed reference."}),e.jsx("p",{children:"Wh/km appears after 25 m of selected-range odometer travel. Existing historical rows predate exact power integration, so their Wh fields remain zero rather than being backfilled with false precision."})]})]}):null]})}function Ge(){Ae({identitySurface:"passive"});const S=Ne(b=>De(b,"fleetReports")),{value:M}=Ie("page",{backgroundTheme:Le}),$=Te(M?.backgroundTheme);return e.jsxs("div",{className:`${$} min-h-screen text-slate-100`,children:[e.jsx(Fe,{}),e.jsx("main",{className:`mx-auto flex min-h-screen w-full max-w-[120rem] flex-col ${Pe} p-1`,children:S?e.jsx(Ve,{}):e.jsxs(de,{title:"Fleet reports unavailable",bodyClassName:"space-y-0.5 p-1 text-sm text-slate-300",children:[e.jsx("p",{children:"This server has not enabled fleet reporting."}),e.jsx(Oe,{className:"button-dark inline-block px-1 py-0.5 text-xs",to:"/",children:"Back to rover page"})]})})]})}export{Ge as default}; -//# sourceMappingURL=FleetReportsApp-DYMSv9Or.js.map +//# sourceMappingURL=FleetReportsApp-ChDcFmTF.js.map diff --git a/server/public/assets/FleetReportsApp-DYMSv9Or.js.map b/server/public/assets/FleetReportsApp-ChDcFmTF.js.map similarity index 99% rename from server/public/assets/FleetReportsApp-DYMSv9Or.js.map rename to server/public/assets/FleetReportsApp-ChDcFmTF.js.map index 016f3724..0617f02a 100644 --- a/server/public/assets/FleetReportsApp-DYMSv9Or.js.map +++ b/server/public/assets/FleetReportsApp-ChDcFmTF.js.map @@ -1 +1 @@ -{"version":3,"file":"FleetReportsApp-DYMSv9Or.js","sources":["../../../webui/node_modules/papaparse/papaparse.min.js","../../../webui/src/reports/FleetReportsApp.jsx"],"sourcesContent":["/* @license\nPapa Parse\nv5.5.4\nhttps://github.com/mholt/PapaParse\nLicense: MIT\n*/\n((e,t)=>{\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof module&&\"undefined\"!=typeof exports?module.exports=t():e.Papa=t()})(this,function r(){var n=\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:void 0!==n?n:{};var d,s=!n.document&&!!n.postMessage,a=n.IS_PAPA_WORKER||!1,o={},h=0,v={};function P(e){return 65279===e.charCodeAt(0)?e.slice(1):e}function u(e){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine=\"\",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},function(e){var t=b(e);t.chunkSize=parseInt(t.chunkSize),e.step||e.chunk||(t.chunkSize=null);this._handle=new i(t),(this._handle.streamer=this)._config=t}.call(this,e),this.parseChunk=function(t,e){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview);if(a)n.postMessage({results:r,workerId:v.WORKER_ID,finished:i});else if(q(this._config.chunk)&&!e){if(this._config.chunk(r,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=r=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(r.data),this._completeResults.errors=this._completeResults.errors.concat(r.errors),this._completeResults.meta=r.meta),this._completed||!i||!q(this._config.complete)||r&&r.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||r&&r.meta.paused||this._nextChunk(),r}this._halted=!0},this._sendError=function(e){q(this._config.error)?this._config.error(e):a&&this._config.error&&n.postMessage({workerId:v.WORKER_ID,error:e,finished:!1})}}function f(e){var r;(e=e||{}).chunkSize||(e.chunkSize=v.RemoteChunkSize),u.call(this,e),this._nextChunk=s?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(r=new XMLHttpRequest,this._config.withCredentials&&(r.withCredentials=this._config.withCredentials),s||(r.onload=y(this._chunkLoaded,this),r.onerror=y(this._chunkError,this)),r.open(this._config.downloadRequestBody?\"POST\":\"GET\",this._input,!s),this._config.downloadRequestHeaders){var e,t=this._config.downloadRequestHeaders;for(e in t)r.setRequestHeader(e,t[e])}var i;this._config.chunkSize&&(i=this._start+this._config.chunkSize-1,r.setRequestHeader(\"Range\",\"bytes=\"+this._start+\"-\"+i));try{r.send(this._config.downloadRequestBody)}catch(e){this._chunkError(e.message)}s&&0===r.status&&this._chunkError()}},this._chunkLoaded=function(){4===r.readyState&&(r.status<200||400<=r.status?this._chunkError():(this._start+=this._config.chunkSize||r.responseText.length,this._finished=!this._config.chunkSize||this._start>=(e=>null!==(e=e.getResponseHeader(\"Content-Range\"))?parseInt(e.substring(e.lastIndexOf(\"/\")+1)):-1)(r),this.parseChunk(r.responseText)))},this._chunkError=function(e){e=r.statusText||e;this._sendError(new Error(e))}}function l(e){(e=e||{}).chunkSize||(e.chunkSize=v.LocalChunkSize),u.call(this,e);var i,r,n=\"undefined\"!=typeof FileReader;this.stream=function(e){this._input=e,r=e.slice||e.webkitSlice||e.mozSlice,n?((i=new FileReader).onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function c(e){var i;u.call(this,e=e||{}),this.stream=function(e){return i=e,this._nextChunk()},this._nextChunk=function(){var e,t;if(!this._finished)return e=this._config.chunkSize,i=e?(t=i.substring(0,e),i.substring(e)):(t=i,\"\"),this._finished=!i,this.parseChunk(t)}}function p(e){u.call(this,e=e||{});var t=[],i=!0,r=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on(\"data\",this._streamData),this._input.on(\"end\",this._streamEnd),this._input.on(\"error\",this._streamError)},this._checkIsFinished=function(){r&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):i=!0},this._streamData=y(function(e){try{t.push(\"string\"==typeof e?e:e.toString(this._config.encoding)),i&&(i=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),r=!0,this._streamData(\"\")},this),this._streamCleanUp=y(function(){this._input.removeListener(\"data\",this._streamData),this._input.removeListener(\"end\",this._streamEnd),this._input.removeListener(\"error\",this._streamError)},this)}function i(m){var n,s,a,t,o=Math.pow(2,53),h=-o,u=/^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/,d=/^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/,i=this,r=0,f=0,l=!1,e=!1,c=[],p={data:[],errors:[],meta:{}};function y(e){return\"greedy\"===m.skipEmptyLines?\"\"===e.join(\"\").trim():1===e.length&&0===e[0].length}function g(){if(p&&a&&(k(\"Delimiter\",\"UndetectableDelimiter\",\"Unable to auto-detect delimiting character; defaulted to '\"+v.DefaultDelimiter+\"'\"),a=!1),m.skipEmptyLines&&(p.data=p.data.filter(function(e){return!y(e)})),_()){if(p)if(Array.isArray(p.data[0])){for(var e=0;_()&&e(e=>(m.dynamicTypingFunction&&void 0===m.dynamicTyping[e]&&(m.dynamicTyping[e]=m.dynamicTypingFunction(e)),!0===(m.dynamicTyping[e]||m.dynamicTyping)))(e)?\"true\"===t||\"TRUE\"===t||\"false\"!==t&&\"FALSE\"!==t&&((e=>{if(u.test(e)){e=parseFloat(e);if(h=c.length?\"__parsed_extra\":c[r]:n,s=m.transform?m.transform(s,n):s);\"__parsed_extra\"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return m.header&&(r>c.length?k(\"FieldMismatch\",\"TooManyFields\",\"Too many fields: expected \"+c.length+\" fields but parsed \"+r,f+t):rm.preview?s.abort():(p.data=p.data[0],t(p,i))))}),this.parse=function(e,t,i){var r=m.quoteChar||'\"',r=(m.newline||(m.newline=this.guessLineEndings(e,r)),a=!1,m.delimiter?q(m.delimiter)&&(m.delimiter=m.delimiter(e),p.meta.delimiter=m.delimiter):((r=((e,t,i,r,n)=>{var s,a,o,h;n=n||[\",\",\"\\t\",\"|\",\";\",v.RECORD_SEP,v.UNIT_SEP];for(var u=0;u=i.length/2?\"\\r\\n\":\"\\r\"}}function U(e){return e.replace(/[.*+?^${}()|[\\]\\\\]/g,\"\\\\$&\")}function E(C){var S=(C=C||{}).delimiter,O=C.newline,x=C.comments,I=C.step,A=C.preview,T=C.fastMode,D=null,L=!1,F=null==C.quoteChar?'\"':C.quoteChar,j=F;if(void 0!==C.escapeChar&&(j=C.escapeChar),(\"string\"!=typeof S||-1=A)return w(!0);break}u.push({type:\"Quotes\",code:\"InvalidQuotes\",message:\"Trailing quote on quoted field is malformed\",row:h.length,index:z}),m++}}else if(x&&0===d.length&&i.substring(z,z+a)===x){if(-1===g)return w();z=g+s,g=i.indexOf(O,z),p=i.indexOf(S,z)}else if(-1!==p&&(p=A)return w(!0)}return E();function k(e){h.push(e),f=z}function v(e){var t=0;return t=-1!==e&&(e=i.substring(m+1,e))&&\"\"===e.trim()?e.length:t}function E(e){return r||(void 0===e&&(e=i.substring(z)),d.push(e),z=n,k(d),o&&R()),w()}function b(e){z=e,k(d),d=[],g=i.indexOf(O,z)}function w(e){if(C.header&&!t&&h.length&&!L){var s=h[0],a=Object.create(null),o=new Set(s);let n=!1;for(let r=0;r{var e;return!!v.WORKERS_SUPPORTED&&(e=(()=>{var e=n.URL||n.webkitURL||null,t=r.toString();return v.BLOB_URL||(v.BLOB_URL=e.createObjectURL(new Blob([\"var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; \",\"(\",t,\")();\"],{type:\"text/javascript\"})))})(),(e=new n.Worker(e)).onmessage=g,e.id=h++,o[e.id]=e)})()).userStep=t.step,i.userChunk=t.chunk,i.userComplete=t.complete,i.userError=t.error,t.step=q(t.step),t.chunk=q(t.chunk),t.complete=q(t.complete),t.error=q(t.error),delete t.worker,i.postMessage({input:e,config:t,workerId:i.id})},v.unparse=function(e,t){var s=!1,_=!0,m=\",\",y=\"\\r\\n\",a='\"',o=a+a,i=!1,r=null,h=!1,u=((()=>{if(\"object\"==typeof t){if(\"string\"!=typeof t.delimiter||v.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(m=t.delimiter),\"boolean\"!=typeof t.quotes&&\"function\"!=typeof t.quotes&&!Array.isArray(t.quotes)||(s=t.quotes),\"boolean\"!=typeof t.skipEmptyLines&&\"string\"!=typeof t.skipEmptyLines||(i=t.skipEmptyLines),\"string\"==typeof t.newline&&(y=t.newline),\"string\"==typeof t.quoteChar&&(a=t.quoteChar,o=a+a),\"boolean\"==typeof t.header&&(_=t.header),Array.isArray(t.columns)){if(0===t.columns.length)throw new Error(\"Option columns is empty\");r=t.columns}void 0!==t.escapeChar&&(o=t.escapeChar+a),t.escapeFormulae instanceof RegExp?h=t.escapeFormulae:\"boolean\"==typeof t.escapeFormulae&&t.escapeFormulae&&(h=/^[=+\\-@\\t\\r].*$/)}})(),new RegExp(U(a),\"g\"));\"string\"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return n(null,e,i);if(\"object\"==typeof e[0])return n(r||Object.keys(e[0]),e,i)}else if(\"object\"==typeof e)return\"string\"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||r),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:\"object\"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||\"object\"==typeof e.data[0]||(e.data=[e.data])),n(e.fields||[],e.data||[],i);throw new Error(\"Unable to serialize unrecognized input\");function n(e,t,i){var r=\"\",n=(\"string\"==typeof e&&(e=JSON.parse(e)),\"string\"==typeof t&&(t=JSON.parse(t)),Array.isArray(e)&&0{for(var i=0;i= 1e6 ? `${number(millimeters / 1e6)} km` : `${number(millimeters / 1000, 1)} m`;\n}\n\nfunction exportCsv(rows) {\n // Papa Parse owns quoting and escaping so new metric columns cannot silently\n // corrupt exports when rover names or confidence explanations contain commas.\n const csv = Papa.unparse(rows.map((rover) => ({\n ...rover,\n batteryHealth: JSON.stringify(rover.batteryHealth),\n })));\n const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }));\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = 'fleet-battery-efficiency.csv';\n anchor.click();\n URL.revokeObjectURL(url);\n}\n\nfunction Metric({ label, value, detail }) {\n return (\n
\n
{label}
\n
{value}
\n {detail ?
{detail}
: null}\n
\n );\n}\n\nfunction SortHeading({ field, sort, onSort, children }) {\n // Sorting is an explicit table interaction rather than hidden column state;\n // the arrow tells users which fleet comparison currently controls row order.\n return ;\n}\n\nfunction BatteryRegistry({ report, refresh }) {\n const socket = useSocket();\n const role = useSessionSelector((state) => state.session?.role || null);\n const canEdit = role === 'admin' || role === 'lockdown';\n const [draft, setDraft] = useState({\n roverId: report.rovers[0]?.roverId || '',\n chemistry: 'unknown',\n ratedCapacityMah: '',\n installedDate: new Date().toISOString().slice(0, 10),\n notes: '',\n });\n const [status, setStatus] = useState('');\n\n function submit(event) {\n event.preventDefault();\n setStatus('Saving…');\n socket.emit('fleetReports:replaceBattery', {\n ...draft,\n ratedCapacityMah: Number(draft.ratedCapacityMah),\n installedAt: new Date(`${draft.installedDate}T12:00:00`).getTime(),\n }, (response = {}) => {\n setStatus(response.error || `Registered ${response.battery?.batteryKey || 'battery'}`);\n if (!response.error) refresh();\n });\n }\n\n return (\n \n
\n \n \n {report.batteryRegistry.map((battery) => (\n \n \n \n \n \n ))}\n
RoverBatteryChemistryRated mAhInstalledRetiredLearned baseline mAhNotes
{battery.roverId}{battery.batteryKey}{battery.chemistry || '--'}{number(battery.ratedCapacityMah, 0)}{timestamp(battery.installedAt)}{timestamp(battery.retiredAt)}{number(battery.healthyBaselineMah, 0)}{battery.notes || '--'}
\n
\n {canEdit ? (\n
\n \n \n \n \n \n
{status}
\n
\n ) : null}\n
\n );\n}\n\nfunction FullReportContent() {\n const [rangeMs, setRangeMs] = useState(RANGE_OPTIONS[1].ms);\n const [rangeEnd, setRangeEnd] = useState(() => Date.now());\n const [query, setQuery] = useState('');\n const [sort, setSort] = useState({ key: 'name', descending: false });\n const { report, loading, error, refresh } = useFleetReport({\n since: rangeEnd - rangeMs,\n until: rangeEnd,\n includeEvents: false,\n });\n\n const rows = useMemo(() => {\n const filtered = (report?.rovers || []).filter((rover) =>\n `${rover.name} ${rover.roverId}`.toLowerCase().includes(query.trim().toLowerCase()),\n );\n return [...filtered].sort((left, right) => {\n const leftValue = sort.key.startsWith('batteryHealth.')\n ? left.batteryHealth?.[sort.key.slice('batteryHealth.'.length)]\n : left[sort.key];\n const rightValue = sort.key.startsWith('batteryHealth.')\n ? right.batteryHealth?.[sort.key.slice('batteryHealth.'.length)]\n : right[sort.key];\n const result = typeof leftValue === 'string'\n ? leftValue.localeCompare(String(rightValue ?? ''))\n : (Number(leftValue) || 0) - (Number(rightValue) || 0);\n return sort.descending ? -result : result;\n });\n }, [query, report, sort]);\n\n function sortBy(key) {\n setSort((current) => ({ key, descending: current.key === key ? !current.descending : false }));\n }\n\n return (\n <>\n \n
\n \n \n \n Back\n
\n {loading && !report ?

Loading fleet metrics…

: null}\n {error ?

{error}

: null}\n {report ? (\n
\n \n \n \n \n \n \n \n \n
\n ) : null}\n
\n\n {report ? (\n <>\n exportCsv(rows)}>Export CSV}\n bodyClassName=\"overflow-x-auto p-0.5\"\n >\n \n \n RoverState\n HealthUsable Ah\n Usable WhConfidence\n Wh/kmMoving Wh/km\n DistanceUsed Wh\n Moving WhStationary Wh\n Moving timeAverage speed\n Maximum speedCharge\n VoltageCurrent\n TemperatureLast data\n \n {rows.map((rover) => {\n const health = rover.batteryHealth;\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n })}\n
{rover.name}{rover.online ? 'online' : 'offline'}{health.capacityRetentionPercent == null ? '--' : `${number(health.capacityRetentionPercent, 1)}%`}{health.measuredUsableMah == null ? '--' : number(health.measuredUsableMah / 1000, 3)}{number(health.measuredUsableWh)}{health.confidence}{rover.overallWhPerKm == null ? `need ${distance(rover.efficiencyDistanceRequiredMm)}` : number(rover.overallWhPerKm)}{number(rover.movingWhPerKm)}{distance(rover.distanceMm)}{number(rover.dischargedWh)}{number(rover.movingDischargedWh)}{number(rover.stationaryDischargedWh)}{duration(rover.movingMs)}{rover.averageSpeedMmPerSecond == null ? '--' : `${number(rover.averageSpeedMmPerSecond)} mm/s`}{rover.maximumSpeedMmPerSecond == null ? '--' : `${number(rover.maximumSpeedMmPerSecond)} mm/s`}{number(rover.latestChargeMah, 0)} mAh{number(rover.averageVoltageMv, 0)} mV{number(rover.averageCurrentMa, 0)} mA{number(rover.maximumTemperatureC, 1)} °C{timestamp(rover.lastSampleAt)}
\n \n\n \n \n \n {rows.map((rover) => {\n const health = rover.batteryHealth;\n return ;\n })}\n
RoverBatteryReference mAhEstimated usable mAhObserved range floorRetentionObservationsAverage depthThroughputLatest evidenceConfidence basis
{rover.name}{health.batteryKey}{number(health.referenceMah, 0)}{number(health.measuredUsableMah, 0)}{number(health.observedUsableFloorMah, 0)}{health.capacityRetentionPercent == null ? '--' : `${number(health.capacityRetentionPercent, 1)}%`}{number(health.observationCount, 0)}{number(health.averageObservationDepthPercent, 1)}%{number(health.dischargedThroughputMah, 0)} mAh{timestamp(health.latestObservationAt)}{health.confidenceReason}
\n
\n\n {report.attention.length ? (\n \n {report.attention.map((item) => )}
RoverSeverityReason
{item.roverId}{item.severity}{item.title}
\n
\n ) : null}\n\n \n\n \n

Energy is integrated per sensor interval from OI voltage and signed current. Existing odometer distance and speed are consumed directly; reporting does not calculate a competing speed value.

\n

Capacity combines partial discharge current with packet 25 charge movement. Every usable observation contributes; deeper observations increase confidence without labeling cycles good or bad. Packet 26 is only a fixed reference.

\n

Wh/km appears after 25 m of selected-range odometer travel. Existing historical rows predate exact power integration, so their Wh fields remain zero rather than being backfilled with false precision.

\n
\n \n ) : null}\n \n );\n}\n\nexport default function FleetReportsApp() {\n useUserIdentitySync({ identitySurface: 'passive' });\n const enabled = useSessionSelector((state) => isFeatureEnabled(state, 'fleetReports'));\n const { value: pageSettings } = useSettingsNamespace('page', { backgroundTheme: DEFAULT_PAGE_THEME_KEY });\n const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);\n return (\n
\n \n
\n {enabled ? : (\n \n

This server has not enabled fleet reporting.

\n Back to rover page\n
\n )}\n
\n
\n );\n}\n"],"names":["e","t","module","this","r","n","d","s","a","o","h","v","P","u","b","i","q","f","y","l","c","p","m","g","k","_","E","U","C","S","O","x","I","A","T","D","L","F","j","z","M","w","R","RANGE_OPTIONS","number","value","digits","timestamp","duration","minutes","distance","millimeters","exportCsv","rows","csv","Papa","rover","url","anchor","Metric","label","detail","jsxs","jsx","SortHeading","field","sort","onSort","children","BatteryRegistry","report","refresh","socket","useSocket","role","useSessionSelector","state","canEdit","draft","setDraft","useState","status","setStatus","submit","event","response","CardFrame","battery","FullReportContent","rangeMs","setRangeMs","rangeEnd","setRangeEnd","query","setQuery","setSort","loading","error","useFleetReport","useMemo","left","right","leftValue","rightValue","result","sortBy","key","current","Fragment","option","Link","health","item","FleetReportsApp","useUserIdentitySync","enabled","isFeatureEnabled","pageSettings","useSettingsNamespace","DEFAULT_PAGE_THEME_KEY","pageBackgroundClass","usePageThemeClass","SocketConnectionPill","themeGapClass"],"mappings":"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","x_google_ignoreList":[0]} \ No newline at end of file +{"version":3,"file":"FleetReportsApp-ChDcFmTF.js","sources":["../../../webui/node_modules/papaparse/papaparse.min.js","../../../webui/src/reports/FleetReportsApp.jsx"],"sourcesContent":["/* @license\nPapa Parse\nv5.5.4\nhttps://github.com/mholt/PapaParse\nLicense: MIT\n*/\n((e,t)=>{\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof module&&\"undefined\"!=typeof exports?module.exports=t():e.Papa=t()})(this,function r(){var n=\"undefined\"!=typeof self?self:\"undefined\"!=typeof window?window:void 0!==n?n:{};var d,s=!n.document&&!!n.postMessage,a=n.IS_PAPA_WORKER||!1,o={},h=0,v={};function P(e){return 65279===e.charCodeAt(0)?e.slice(1):e}function u(e){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine=\"\",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},function(e){var t=b(e);t.chunkSize=parseInt(t.chunkSize),e.step||e.chunk||(t.chunkSize=null);this._handle=new i(t),(this._handle.streamer=this)._config=t}.call(this,e),this.parseChunk=function(t,e){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview);if(a)n.postMessage({results:r,workerId:v.WORKER_ID,finished:i});else if(q(this._config.chunk)&&!e){if(this._config.chunk(r,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=r=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(r.data),this._completeResults.errors=this._completeResults.errors.concat(r.errors),this._completeResults.meta=r.meta),this._completed||!i||!q(this._config.complete)||r&&r.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||r&&r.meta.paused||this._nextChunk(),r}this._halted=!0},this._sendError=function(e){q(this._config.error)?this._config.error(e):a&&this._config.error&&n.postMessage({workerId:v.WORKER_ID,error:e,finished:!1})}}function f(e){var r;(e=e||{}).chunkSize||(e.chunkSize=v.RemoteChunkSize),u.call(this,e),this._nextChunk=s?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(r=new XMLHttpRequest,this._config.withCredentials&&(r.withCredentials=this._config.withCredentials),s||(r.onload=y(this._chunkLoaded,this),r.onerror=y(this._chunkError,this)),r.open(this._config.downloadRequestBody?\"POST\":\"GET\",this._input,!s),this._config.downloadRequestHeaders){var e,t=this._config.downloadRequestHeaders;for(e in t)r.setRequestHeader(e,t[e])}var i;this._config.chunkSize&&(i=this._start+this._config.chunkSize-1,r.setRequestHeader(\"Range\",\"bytes=\"+this._start+\"-\"+i));try{r.send(this._config.downloadRequestBody)}catch(e){this._chunkError(e.message)}s&&0===r.status&&this._chunkError()}},this._chunkLoaded=function(){4===r.readyState&&(r.status<200||400<=r.status?this._chunkError():(this._start+=this._config.chunkSize||r.responseText.length,this._finished=!this._config.chunkSize||this._start>=(e=>null!==(e=e.getResponseHeader(\"Content-Range\"))?parseInt(e.substring(e.lastIndexOf(\"/\")+1)):-1)(r),this.parseChunk(r.responseText)))},this._chunkError=function(e){e=r.statusText||e;this._sendError(new Error(e))}}function l(e){(e=e||{}).chunkSize||(e.chunkSize=v.LocalChunkSize),u.call(this,e);var i,r,n=\"undefined\"!=typeof FileReader;this.stream=function(e){this._input=e,r=e.slice||e.webkitSlice||e.mozSlice,n?((i=new FileReader).onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function c(e){var i;u.call(this,e=e||{}),this.stream=function(e){return i=e,this._nextChunk()},this._nextChunk=function(){var e,t;if(!this._finished)return e=this._config.chunkSize,i=e?(t=i.substring(0,e),i.substring(e)):(t=i,\"\"),this._finished=!i,this.parseChunk(t)}}function p(e){u.call(this,e=e||{});var t=[],i=!0,r=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on(\"data\",this._streamData),this._input.on(\"end\",this._streamEnd),this._input.on(\"error\",this._streamError)},this._checkIsFinished=function(){r&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):i=!0},this._streamData=y(function(e){try{t.push(\"string\"==typeof e?e:e.toString(this._config.encoding)),i&&(i=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),r=!0,this._streamData(\"\")},this),this._streamCleanUp=y(function(){this._input.removeListener(\"data\",this._streamData),this._input.removeListener(\"end\",this._streamEnd),this._input.removeListener(\"error\",this._streamError)},this)}function i(m){var n,s,a,t,o=Math.pow(2,53),h=-o,u=/^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/,d=/^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/,i=this,r=0,f=0,l=!1,e=!1,c=[],p={data:[],errors:[],meta:{}};function y(e){return\"greedy\"===m.skipEmptyLines?\"\"===e.join(\"\").trim():1===e.length&&0===e[0].length}function g(){if(p&&a&&(k(\"Delimiter\",\"UndetectableDelimiter\",\"Unable to auto-detect delimiting character; defaulted to '\"+v.DefaultDelimiter+\"'\"),a=!1),m.skipEmptyLines&&(p.data=p.data.filter(function(e){return!y(e)})),_()){if(p)if(Array.isArray(p.data[0])){for(var e=0;_()&&e(e=>(m.dynamicTypingFunction&&void 0===m.dynamicTyping[e]&&(m.dynamicTyping[e]=m.dynamicTypingFunction(e)),!0===(m.dynamicTyping[e]||m.dynamicTyping)))(e)?\"true\"===t||\"TRUE\"===t||\"false\"!==t&&\"FALSE\"!==t&&((e=>{if(u.test(e)){e=parseFloat(e);if(h=c.length?\"__parsed_extra\":c[r]:n,s=m.transform?m.transform(s,n):s);\"__parsed_extra\"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return m.header&&(r>c.length?k(\"FieldMismatch\",\"TooManyFields\",\"Too many fields: expected \"+c.length+\" fields but parsed \"+r,f+t):rm.preview?s.abort():(p.data=p.data[0],t(p,i))))}),this.parse=function(e,t,i){var r=m.quoteChar||'\"',r=(m.newline||(m.newline=this.guessLineEndings(e,r)),a=!1,m.delimiter?q(m.delimiter)&&(m.delimiter=m.delimiter(e),p.meta.delimiter=m.delimiter):((r=((e,t,i,r,n)=>{var s,a,o,h;n=n||[\",\",\"\\t\",\"|\",\";\",v.RECORD_SEP,v.UNIT_SEP];for(var u=0;u=i.length/2?\"\\r\\n\":\"\\r\"}}function U(e){return e.replace(/[.*+?^${}()|[\\]\\\\]/g,\"\\\\$&\")}function E(C){var S=(C=C||{}).delimiter,O=C.newline,x=C.comments,I=C.step,A=C.preview,T=C.fastMode,D=null,L=!1,F=null==C.quoteChar?'\"':C.quoteChar,j=F;if(void 0!==C.escapeChar&&(j=C.escapeChar),(\"string\"!=typeof S||-1=A)return w(!0);break}u.push({type:\"Quotes\",code:\"InvalidQuotes\",message:\"Trailing quote on quoted field is malformed\",row:h.length,index:z}),m++}}else if(x&&0===d.length&&i.substring(z,z+a)===x){if(-1===g)return w();z=g+s,g=i.indexOf(O,z),p=i.indexOf(S,z)}else if(-1!==p&&(p=A)return w(!0)}return E();function k(e){h.push(e),f=z}function v(e){var t=0;return t=-1!==e&&(e=i.substring(m+1,e))&&\"\"===e.trim()?e.length:t}function E(e){return r||(void 0===e&&(e=i.substring(z)),d.push(e),z=n,k(d),o&&R()),w()}function b(e){z=e,k(d),d=[],g=i.indexOf(O,z)}function w(e){if(C.header&&!t&&h.length&&!L){var s=h[0],a=Object.create(null),o=new Set(s);let n=!1;for(let r=0;r{var e;return!!v.WORKERS_SUPPORTED&&(e=(()=>{var e=n.URL||n.webkitURL||null,t=r.toString();return v.BLOB_URL||(v.BLOB_URL=e.createObjectURL(new Blob([\"var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; \",\"(\",t,\")();\"],{type:\"text/javascript\"})))})(),(e=new n.Worker(e)).onmessage=g,e.id=h++,o[e.id]=e)})()).userStep=t.step,i.userChunk=t.chunk,i.userComplete=t.complete,i.userError=t.error,t.step=q(t.step),t.chunk=q(t.chunk),t.complete=q(t.complete),t.error=q(t.error),delete t.worker,i.postMessage({input:e,config:t,workerId:i.id})},v.unparse=function(e,t){var s=!1,_=!0,m=\",\",y=\"\\r\\n\",a='\"',o=a+a,i=!1,r=null,h=!1,u=((()=>{if(\"object\"==typeof t){if(\"string\"!=typeof t.delimiter||v.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(m=t.delimiter),\"boolean\"!=typeof t.quotes&&\"function\"!=typeof t.quotes&&!Array.isArray(t.quotes)||(s=t.quotes),\"boolean\"!=typeof t.skipEmptyLines&&\"string\"!=typeof t.skipEmptyLines||(i=t.skipEmptyLines),\"string\"==typeof t.newline&&(y=t.newline),\"string\"==typeof t.quoteChar&&(a=t.quoteChar,o=a+a),\"boolean\"==typeof t.header&&(_=t.header),Array.isArray(t.columns)){if(0===t.columns.length)throw new Error(\"Option columns is empty\");r=t.columns}void 0!==t.escapeChar&&(o=t.escapeChar+a),t.escapeFormulae instanceof RegExp?h=t.escapeFormulae:\"boolean\"==typeof t.escapeFormulae&&t.escapeFormulae&&(h=/^[=+\\-@\\t\\r].*$/)}})(),new RegExp(U(a),\"g\"));\"string\"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return n(null,e,i);if(\"object\"==typeof e[0])return n(r||Object.keys(e[0]),e,i)}else if(\"object\"==typeof e)return\"string\"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||r),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:\"object\"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||\"object\"==typeof e.data[0]||(e.data=[e.data])),n(e.fields||[],e.data||[],i);throw new Error(\"Unable to serialize unrecognized input\");function n(e,t,i){var r=\"\",n=(\"string\"==typeof e&&(e=JSON.parse(e)),\"string\"==typeof t&&(t=JSON.parse(t)),Array.isArray(e)&&0{for(var i=0;i= 1e6 ? `${number(millimeters / 1e6)} km` : `${number(millimeters / 1000, 1)} m`;\n}\n\nfunction exportCsv(rows) {\n // Papa Parse owns quoting and escaping so new metric columns cannot silently\n // corrupt exports when rover names or confidence explanations contain commas.\n const csv = Papa.unparse(rows.map((rover) => ({\n ...rover,\n batteryHealth: JSON.stringify(rover.batteryHealth),\n })));\n const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }));\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = 'fleet-battery-efficiency.csv';\n anchor.click();\n URL.revokeObjectURL(url);\n}\n\nfunction Metric({ label, value, detail }) {\n return (\n
\n
{label}
\n
{value}
\n {detail ?
{detail}
: null}\n
\n );\n}\n\nfunction SortHeading({ field, sort, onSort, children }) {\n // Sorting is an explicit table interaction rather than hidden column state;\n // the arrow tells users which fleet comparison currently controls row order.\n return ;\n}\n\nfunction BatteryRegistry({ report, refresh }) {\n const socket = useSocket();\n const role = useSessionSelector((state) => state.session?.role || null);\n const canEdit = role === 'admin' || role === 'lockdown';\n const [draft, setDraft] = useState({\n roverId: report.rovers[0]?.roverId || '',\n chemistry: 'unknown',\n ratedCapacityMah: '',\n installedDate: new Date().toISOString().slice(0, 10),\n notes: '',\n });\n const [status, setStatus] = useState('');\n\n function submit(event) {\n event.preventDefault();\n setStatus('Saving…');\n socket.emit('fleetReports:replaceBattery', {\n ...draft,\n ratedCapacityMah: Number(draft.ratedCapacityMah),\n installedAt: new Date(`${draft.installedDate}T12:00:00`).getTime(),\n }, (response = {}) => {\n setStatus(response.error || `Registered ${response.battery?.batteryKey || 'battery'}`);\n if (!response.error) refresh();\n });\n }\n\n return (\n \n
\n \n \n {report.batteryRegistry.map((battery) => (\n \n \n \n \n \n ))}\n
RoverBatteryChemistryRated mAhInstalledRetiredLearned baseline mAhNotes
{battery.roverId}{battery.batteryKey}{battery.chemistry || '--'}{number(battery.ratedCapacityMah, 0)}{timestamp(battery.installedAt)}{timestamp(battery.retiredAt)}{number(battery.healthyBaselineMah, 0)}{battery.notes || '--'}
\n
\n {canEdit ? (\n
\n \n \n \n \n \n
{status}
\n
\n ) : null}\n
\n );\n}\n\nfunction FullReportContent() {\n const [rangeMs, setRangeMs] = useState(RANGE_OPTIONS[1].ms);\n const [rangeEnd, setRangeEnd] = useState(() => Date.now());\n const [query, setQuery] = useState('');\n const [sort, setSort] = useState({ key: 'name', descending: false });\n const { report, loading, error, refresh } = useFleetReport({\n since: rangeEnd - rangeMs,\n until: rangeEnd,\n includeEvents: false,\n });\n\n const rows = useMemo(() => {\n const filtered = (report?.rovers || []).filter((rover) =>\n `${rover.name} ${rover.roverId}`.toLowerCase().includes(query.trim().toLowerCase()),\n );\n return [...filtered].sort((left, right) => {\n const leftValue = sort.key.startsWith('batteryHealth.')\n ? left.batteryHealth?.[sort.key.slice('batteryHealth.'.length)]\n : left[sort.key];\n const rightValue = sort.key.startsWith('batteryHealth.')\n ? right.batteryHealth?.[sort.key.slice('batteryHealth.'.length)]\n : right[sort.key];\n const result = typeof leftValue === 'string'\n ? leftValue.localeCompare(String(rightValue ?? ''))\n : (Number(leftValue) || 0) - (Number(rightValue) || 0);\n return sort.descending ? -result : result;\n });\n }, [query, report, sort]);\n\n function sortBy(key) {\n setSort((current) => ({ key, descending: current.key === key ? !current.descending : false }));\n }\n\n return (\n <>\n \n
\n \n \n \n Back\n
\n {loading && !report ?

Loading fleet metrics…

: null}\n {error ?

{error}

: null}\n {report ? (\n
\n \n \n \n \n \n \n \n \n
\n ) : null}\n
\n\n {report ? (\n <>\n exportCsv(rows)}>Export CSV}\n bodyClassName=\"overflow-x-auto p-0.5\"\n >\n \n \n RoverState\n HealthUsable Ah\n Usable WhConfidence\n Wh/kmMoving Wh/km\n DistanceUsed Wh\n Moving WhStationary Wh\n Moving timeAverage speed\n Maximum speedCharge\n VoltageCurrent\n TemperatureLast data\n \n {rows.map((rover) => {\n const health = rover.batteryHealth;\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n })}\n
{rover.name}{rover.online ? 'online' : 'offline'}{health.capacityRetentionPercent == null ? '--' : `${number(health.capacityRetentionPercent, 1)}%`}{health.measuredUsableMah == null ? '--' : number(health.measuredUsableMah / 1000, 3)}{number(health.measuredUsableWh)}{health.confidence}{rover.overallWhPerKm == null ? `need ${distance(rover.efficiencyDistanceRequiredMm)}` : number(rover.overallWhPerKm)}{number(rover.movingWhPerKm)}{distance(rover.distanceMm)}{number(rover.dischargedWh)}{number(rover.movingDischargedWh)}{number(rover.stationaryDischargedWh)}{duration(rover.movingMs)}{rover.averageSpeedMmPerSecond == null ? '--' : `${number(rover.averageSpeedMmPerSecond)} mm/s`}{rover.maximumSpeedMmPerSecond == null ? '--' : `${number(rover.maximumSpeedMmPerSecond)} mm/s`}{number(rover.latestChargeMah, 0)} mAh{number(rover.averageVoltageMv, 0)} mV{number(rover.averageCurrentMa, 0)} mA{number(rover.maximumTemperatureC, 1)} °C{timestamp(rover.lastSampleAt)}
\n \n\n \n \n \n {rows.map((rover) => {\n const health = rover.batteryHealth;\n return ;\n })}\n
RoverBatteryReference mAhEstimated usable mAhObserved range floorRetentionObservationsAverage depthThroughputLatest evidenceConfidence basis
{rover.name}{health.batteryKey}{number(health.referenceMah, 0)}{number(health.measuredUsableMah, 0)}{number(health.observedUsableFloorMah, 0)}{health.capacityRetentionPercent == null ? '--' : `${number(health.capacityRetentionPercent, 1)}%`}{number(health.observationCount, 0)}{number(health.averageObservationDepthPercent, 1)}%{number(health.dischargedThroughputMah, 0)} mAh{timestamp(health.latestObservationAt)}{health.confidenceReason}
\n
\n\n {report.attention.length ? (\n \n {report.attention.map((item) => )}
RoverSeverityReason
{item.roverId}{item.severity}{item.title}
\n
\n ) : null}\n\n \n\n \n

Energy is integrated per sensor interval from OI voltage and signed current. Existing odometer distance and speed are consumed directly; reporting does not calculate a competing speed value.

\n

Capacity combines partial discharge current with packet 25 charge movement. Every usable observation contributes; deeper observations increase confidence without labeling cycles good or bad. Packet 26 is only a fixed reference.

\n

Wh/km appears after 25 m of selected-range odometer travel. Existing historical rows predate exact power integration, so their Wh fields remain zero rather than being backfilled with false precision.

\n
\n \n ) : null}\n \n );\n}\n\nexport default function FleetReportsApp() {\n useUserIdentitySync({ identitySurface: 'passive' });\n const enabled = useSessionSelector((state) => isFeatureEnabled(state, 'fleetReports'));\n const { value: pageSettings } = useSettingsNamespace('page', { backgroundTheme: DEFAULT_PAGE_THEME_KEY });\n const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);\n return (\n
\n \n
\n {enabled ? : (\n \n

This server has not enabled fleet reporting.

\n Back to rover page\n
\n )}\n
\n
\n );\n}\n"],"names":["e","t","module","this","r","n","d","s","a","o","h","v","P","u","b","i","q","f","y","l","c","p","m","g","k","_","E","U","C","S","O","x","I","A","T","D","L","F","j","z","M","w","R","RANGE_OPTIONS","number","value","digits","timestamp","duration","minutes","distance","millimeters","exportCsv","rows","csv","Papa","rover","url","anchor","Metric","label","detail","jsxs","jsx","SortHeading","field","sort","onSort","children","BatteryRegistry","report","refresh","socket","useSocket","role","useSessionSelector","state","canEdit","draft","setDraft","useState","status","setStatus","submit","event","response","CardFrame","battery","FullReportContent","rangeMs","setRangeMs","rangeEnd","setRangeEnd","query","setQuery","setSort","loading","error","useFleetReport","useMemo","left","right","leftValue","rightValue","result","sortBy","key","current","Fragment","option","Link","health","item","FleetReportsApp","useUserIdentitySync","enabled","isFeatureEnabled","pageSettings","useSettingsNamespace","DEFAULT_PAGE_THEME_KEY","pageBackgroundClass","usePageThemeClass","SocketConnectionPill","themeGapClass"],"mappings":"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","x_google_ignoreList":[0]} \ No newline at end of file diff --git a/server/public/assets/index-B5TEaoXl.css b/server/public/assets/index-B5TEaoXl.css new file mode 100644 index 00000000..6760da50 --- /dev/null +++ b/server/public/assets/index-B5TEaoXl.css @@ -0,0 +1 @@ +*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.\!panel,.panel{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));padding:0;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.panel-section{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity));padding:0;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.panel-muted{border-radius:.375rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.surface{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.surface-muted{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity))}.field-input{border-radius:.375rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.field-input::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.field-input::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.field-input:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1;--tw-ring-color: rgb(14 165 233 / var(--tw-ring-opacity))}.button-dark{border-radius:.375rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(125 211 252 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity));padding:.125rem;font-size:.875rem;line-height:1.25rem;font-weight:500;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.button-dark:hover{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.button-danger{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity));padding:.125rem;font-size:.875rem;line-height:1.25rem;font-weight:500;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.button-danger:hover{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.inset-x-0{left:0;right:0}.inset-x-1{left:.25rem;right:.25rem}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-0\.5{bottom:.125rem}.bottom-1{bottom:.25rem}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.bottom-40{bottom:10rem}.bottom-\[8\.5rem\]{bottom:8.5rem}.bottom-\[calc\(100\%\+0\.125rem\)\]{bottom:calc(100% + .125rem)}.left-0{left:0}.left-0\.5{left:.125rem}.left-1{left:.25rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-4{left:1rem}.left-\[1\.979rem\]{left:1.979rem}.left-\[5\.338rem\]{left:5.338rem}.left-\[61\%\]{left:61%}.left-\[90\%\]{left:90%}.right-0{right:0}.right-0\.5{right:.125rem}.right-1{right:.25rem}.right-10{right:2.5rem}.right-2{right:.5rem}.right-\[8\.5rem\]{right:8.5rem}.top-0{top:0}.top-0\.5{top:.125rem}.top-1{top:.25rem}.top-1\/2{top:50%}.top-10{top:2.5rem}.top-6{top:1.5rem}.top-\[0\.662rem\]{top:.662rem}.top-\[4\.021rem\]{top:4.021rem}.top-\[58\%\]{top:58%}.top-\[61\%\]{top:61%}.top-\[8\.5rem\]{top:8.5rem}.top-\[90\%\]{top:90%}.top-full{top:100%}.z-0{z-index:0}.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-\[1000\]{z-index:1000}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[120\]{z-index:120}.z-\[130\]{z-index:130}.z-\[2000\]{z-index:2000}.z-\[25\]{z-index:25}.z-\[55\]{z-index:55}.z-\[60\]{z-index:60}.z-\[70\]{z-index:70}.order-1{order:1}.order-2{order:2}.col-span-2{grid-column:span 2 / span 2}.col-span-full{grid-column:1 / -1}.row-span-2{grid-row:span 2 / span 2}.m-0{margin:0}.-mx-0\.5{margin-left:-.125rem;margin-right:-.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-\[-0\.125rem\]{margin-top:-.125rem;margin-bottom:-.125rem}.-mt-0\.5{margin-top:-.125rem}.mb-0\.5{margin-bottom:.125rem}.mb-2{margin-bottom:.5rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.mr-0{margin-right:0}.mr-0\.5{margin-right:.125rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-\[0\.35vh\]{margin-top:.35vh}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.\!block{display:block!important}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-\[4\/3\]{aspect-ratio:4/3}.aspect-square{aspect-ratio:1 / 1}.aspect-video{aspect-ratio:16 / 9}.\!h-28{height:7rem!important}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-28{height:7rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-40{height:10rem}.h-44{height:11rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-52{height:13rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-80{height:20rem}.h-9{height:2.25rem}.h-\[100dvh\]{height:100dvh}.h-\[25\.5rem\]{height:25.5rem}.h-\[2px\]{height:2px}.h-\[3\.5rem\]{height:3.5rem}.h-\[70\%\]{height:70%}.h-\[7rem\]{height:7rem}.h-\[8\.5rem\]{height:8.5rem}.h-\[8vh\]{height:8vh}.h-\[calc\(100dvh-0\.25rem\)\]{height:calc(100dvh - .25rem)}.h-\[calc\(1rem\+0\.25rem\)\]{height:1.25rem}.h-\[min\(100svh\,32rem\)\]{height:min(100svh,32rem)}.h-\[min\(60svh\,24rem\)\]{height:min(60svh,24rem)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-24{max-height:6rem}.max-h-32{max-height:8rem}.max-h-36{max-height:9rem}.max-h-40{max-height:10rem}.max-h-52{max-height:13rem}.max-h-72{max-height:18rem}.max-h-80{max-height:20rem}.max-h-\[14rem\]{max-height:14rem}.max-h-\[36rem\]{max-height:36rem}.max-h-\[70vh\]{max-height:70vh}.max-h-\[72vh\]{max-height:72vh}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-full{max-height:100%}.min-h-0{min-height:0px}.min-h-10{min-height:2.5rem}.min-h-12{min-height:3rem}.min-h-14{min-height:3.5rem}.min-h-5{min-height:1.25rem}.min-h-56{min-height:14rem}.min-h-7{min-height:1.75rem}.min-h-8{min-height:2rem}.min-h-9{min-height:2.25rem}.min-h-\[100dvh\]{min-height:100dvh}.min-h-\[10rem\]{min-height:10rem}.min-h-\[12rem\]{min-height:12rem}.min-h-\[14rem\]{min-height:14rem}.min-h-\[16rem\]{min-height:16rem}.min-h-\[18rem\]{min-height:18rem}.min-h-\[24rem\]{min-height:24rem}.min-h-\[3\.5rem\]{min-height:3.5rem}.min-h-\[4\.5rem\]{min-height:4.5rem}.min-h-\[4rem\]{min-height:4rem}.min-h-\[7rem\]{min-height:7rem}.min-h-\[calc\(100dvh-0\.25rem\)\]{min-height:calc(100dvh - .25rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/5{width:60%}.w-4{width:1rem}.w-4\/5{width:80%}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-\[100vw\]{width:100vw}.w-\[12\.5rem\]{width:12.5rem}.w-\[12rem\]{width:12rem}.w-\[14rem\]{width:14rem}.w-\[20\.5rem\]{width:20.5rem}.w-\[22rem\]{width:22rem}.w-\[25\.5rem\]{width:25.5rem}.w-\[2px\]{width:2px}.w-\[3\.25rem\]{width:3.25rem}.w-\[4\.5rem\]{width:4.5rem}.w-\[45\%\]{width:45%}.w-\[5\.5rem\]{width:5.5rem}.w-\[55\%\]{width:55%}.w-\[5rem\]{width:5rem}.w-\[8\.5rem\]{width:8.5rem}.w-\[9rem\]{width:9rem}.w-\[calc\(1rem\+0\.25rem\)\]{width:1.25rem}.w-\[min\(20rem\,calc\(100vw-1rem\)\)\]{width:min(20rem,calc(100vw - 1rem))}.w-\[min\(30rem\,80\%\)\]{width:min(30rem,80%)}.w-\[min\(32rem\,80\%\)\]{width:min(32rem,80%)}.w-\[min\(32rem\,calc\(80vw-1\.25rem\)\)\]{width:min(32rem,calc(80vw - 1.25rem))}.w-\[min\(34vw\,34rem\)\]{width:min(34vw,34rem)}.w-\[min\(36rem\,82\%\)\]{width:min(36rem,82%)}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:0px}.min-w-16{min-width:4rem}.min-w-20{min-width:5rem}.min-w-28{min-width:7rem}.min-w-56{min-width:14rem}.min-w-\[0\]{min-width:0}.min-w-\[3\.7rem\]{min-width:3.7rem}.min-w-\[5\.5rem\]{min-width:5.5rem}.min-w-\[calc\(50\%_-_0\.125rem\)\]{min-width:calc(50% - .125rem)}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-40{max-width:10rem}.max-w-4xl{max-width:56rem}.max-w-64{max-width:16rem}.max-w-7xl{max-width:80rem}.max-w-\[120rem\]{max-width:120rem}.max-w-\[24rem\]{max-width:24rem}.max-w-\[40vw\]{max-width:40vw}.max-w-\[55\%\]{max-width:55%}.max-w-\[70\%\]{max-width:70%}.max-w-\[70vw\]{max-width:70vw}.max-w-\[80vw\]{max-width:80vw}.max-w-\[92\%\]{max-width:92%}.max-w-\[92vw\]{max-width:92vw}.max-w-\[94\%\]{max-width:94%}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-\[0\.72\]{flex:.72}.flex-\[1\.1\]{flex:1.1}.flex-\[1\.22\]{flex:1.22}.flex-\[1\.28\]{flex:1.28}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-\[0\.75\]{flex-grow:.75}.grow-\[0\.9\]{flex-grow:.9}.grow-\[1\]{flex-grow:1}.basis-0{flex-basis:0px}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-45{--tw-rotate: -45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-90{--tw-rotate: -90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-\[135deg\]{--tw-rotate: -135deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-45{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-\[135deg\]{--tw-rotate: 135deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.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-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[1\.5rem_minmax\(0\,1fr\)_3rem\]{grid-template-columns:1.5rem minmax(0,1fr) 3rem}.grid-cols-\[minmax\(0\,1\.2fr\)_repeat\(4\,minmax\(0\,1fr\)\)\]{grid-template-columns:minmax(0,1.2fr) repeat(4,minmax(0,1fr))}.grid-cols-\[minmax\(0\,1\.35fr\)_minmax\(0\,0\.95fr\)\]{grid-template-columns:minmax(0,1.35fr) minmax(0,.95fr)}.grid-cols-\[minmax\(0\,1\.3fr\)_minmax\(0\,0\.22fr\)\]{grid-template-columns:minmax(0,1.3fr) minmax(0,.22fr)}.grid-cols-\[minmax\(0\,1\.45fr\)_minmax\(0\,0\.85fr\)\]{grid-template-columns:minmax(0,1.45fr) minmax(0,.85fr)}.grid-cols-\[minmax\(0\,1\.6fr\)_minmax\(16rem\,0\.7fr\)\]{grid-template-columns:minmax(0,1.6fr) minmax(16rem,.7fr)}.grid-cols-\[minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr)}.grid-cols-\[minmax\(0\,1fr\)_13rem\]{grid-template-columns:minmax(0,1fr) 13rem}.grid-cols-\[minmax\(0\,1fr\)_14rem\]{grid-template-columns:minmax(0,1fr) 14rem}.grid-cols-\[minmax\(0\,1fr\)_2\.5rem\]{grid-template-columns:minmax(0,1fr) 2.5rem}.grid-cols-\[minmax\(0\,1fr\)_20rem\]{grid-template-columns:minmax(0,1fr) 20rem}.grid-cols-\[minmax\(0\,1fr\)_2rem\]{grid-template-columns:minmax(0,1fr) 2rem}.grid-cols-\[minmax\(0\,1fr\)_4\.75rem\]{grid-template-columns:minmax(0,1fr) 4.75rem}.grid-cols-\[minmax\(0\,1fr\)_8rem\]{grid-template-columns:minmax(0,1fr) 8rem}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,0\.7fr\)_minmax\(0\,1\.9fr\)_minmax\(0\,0\.7fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,.7fr) minmax(0,1.9fr) minmax(0,.7fr)}.grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.grid-cols-\[minmax\(17rem\,1fr\)_minmax\(0\,133\.333vh\)_minmax\(17rem\,1fr\)\]{grid-template-columns:minmax(17rem,1fr) minmax(0,133.333vh) minmax(17rem,1fr)}.grid-cols-\[repeat\(auto-fit\,minmax\(min\(22\.5rem\,100\%\)\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(min(22.5rem,100%),1fr))}.grid-cols-\[repeat\(auto-fit\,minmax\(min\(9rem\,100\%\)\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(min(9rem,100%),1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr}.grid-rows-\[auto_auto_1fr\]{grid-template-rows:auto auto 1fr}.grid-rows-\[auto_auto_1fr_auto\]{grid-template-rows:auto auto 1fr auto}.grid-rows-\[auto_auto_auto_1fr\]{grid-template-rows:auto auto auto 1fr}.grid-rows-\[auto_minmax\(0\,1fr\)\]{grid-template-rows:auto minmax(0,1fr)}.grid-rows-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-rows:auto minmax(0,1fr) auto}.grid-rows-\[minmax\(0\,1fr\)\]{grid-template-rows:minmax(0,1fr)}.grid-rows-\[minmax\(0\,1fr\)_auto\]{grid-template-rows:minmax(0,1fr) auto}.grid-rows-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)_minmax\(0\,1fr\)\]{grid-template-rows:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr)}.grid-rows-\[minmax\(0\,1fr\)_minmax\(7rem\,0\.22fr\)\]{grid-template-rows:minmax(0,1fr) minmax(7rem,.22fr)}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-stretch{justify-content:stretch}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-\[0\.4vh\]{gap:.4vh}.gap-\[0\.55vh\]{gap:.55vh}.gap-\[0\.8vw\]{gap:.8vw}.gap-\[1vh\]{gap:1vh}.gap-\[1vw\]{gap:1vw}.gap-\[3vh\]{gap:3vh}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-0\.5{row-gap:.125rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-r-xl{border-top-right-radius:.75rem;border-bottom-right-radius:.75rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-bl-\[4\.25rem\]{border-bottom-left-radius:4.25rem}.rounded-bl-xl{border-bottom-left-radius:.75rem}.rounded-br-\[12\.75rem\]{border-bottom-right-radius:12.75rem}.rounded-br-\[4\.25rem\]{border-bottom-right-radius:4.25rem}.rounded-br-xl{border-bottom-right-radius:.75rem}.rounded-tl-\[4\.25rem\]{border-top-left-radius:4.25rem}.rounded-tl-lg{border-top-left-radius:.5rem}.rounded-tr-\[4\.25rem\]{border-top-right-radius:4.25rem}.rounded-tr-xl{border-top-right-radius:.75rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity))}.border-amber-200\/30{border-color:#fde68a4d}.border-amber-200\/70{border-color:#fde68ab3}.border-amber-200\/90{border-color:#fde68ae6}.border-amber-300\/60{border-color:#fcd34d99}.border-amber-300\/70{border-color:#fcd34db3}.border-amber-300\/80{border-color:#fcd34dcc}.border-amber-400{--tw-border-opacity: 1;border-color:rgb(251 191 36 / var(--tw-border-opacity))}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-400\/60{border-color:#fbbf2499}.border-amber-500\/50{border-color:#f59e0b80}.border-amber-600\/60{border-color:#d9770699}.border-amber-700\/60{border-color:#b4530999}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.border-cyan-200{--tw-border-opacity: 1;border-color:rgb(165 243 252 / var(--tw-border-opacity))}.border-cyan-200\/90{border-color:#a5f3fce6}.border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.border-cyan-300\/60{border-color:#67e8f999}.border-cyan-300\/70{border-color:#67e8f9b3}.border-cyan-300\/80{border-color:#67e8f9cc}.border-cyan-500\/40{border-color:#06b6d466}.border-emerald-100\/80{border-color:#d1fae5cc}.border-emerald-200{--tw-border-opacity: 1;border-color:rgb(167 243 208 / var(--tw-border-opacity))}.border-emerald-200\/70{border-color:#a7f3d0b3}.border-emerald-200\/90{border-color:#a7f3d0e6}.border-emerald-300{--tw-border-opacity: 1;border-color:rgb(110 231 183 / var(--tw-border-opacity))}.border-emerald-300\/70{border-color:#6ee7b7b3}.border-emerald-400\/60{border-color:#34d39999}.border-emerald-500\/40{border-color:#10b98166}.border-emerald-700\/60{border-color:#04785799}.border-emerald-700\/70{border-color:#047857b3}.border-emerald-950{--tw-border-opacity: 1;border-color:rgb(2 44 34 / var(--tw-border-opacity))}.border-fuchsia-200{--tw-border-opacity: 1;border-color:rgb(245 208 254 / var(--tw-border-opacity))}.border-fuchsia-300\/70{border-color:#f0abfcb3}.border-green-400{--tw-border-opacity: 1;border-color:rgb(74 222 128 / var(--tw-border-opacity))}.border-indigo-200\/70{border-color:#c7d2feb3}.border-indigo-200\/95{border-color:#c7d2fef2}.border-indigo-300\/70{border-color:#a5b4fcb3}.border-indigo-400\/30{border-color:#818cf84d}.border-indigo-700{--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}.border-indigo-700\/60{border-color:#4338ca99}.border-neutral-500{--tw-border-opacity: 1;border-color:rgb(115 115 115 / var(--tw-border-opacity))}.border-neutral-500\/50{border-color:#73737380}.border-neutral-500\/60{border-color:#73737399}.border-neutral-500\/70{border-color:#737373b3}.border-neutral-600{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity))}.border-neutral-700{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity))}.border-neutral-700\/70{border-color:#404040b3}.border-pink-200{--tw-border-opacity: 1;border-color:rgb(251 207 232 / var(--tw-border-opacity))}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity))}.border-red-300\/80{border-color:#fca5a5cc}.border-red-300\/90{border-color:#fca5a5e6}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity))}.border-red-400\/60{border-color:#f8717199}.border-rose-300{--tw-border-opacity: 1;border-color:rgb(253 164 175 / var(--tw-border-opacity))}.border-sky-200{--tw-border-opacity: 1;border-color:rgb(186 230 253 / var(--tw-border-opacity))}.border-sky-200\/70{border-color:#bae6fdb3}.border-sky-300{--tw-border-opacity: 1;border-color:rgb(125 211 252 / var(--tw-border-opacity))}.border-sky-300\/70{border-color:#7dd3fcb3}.border-sky-400\/50{border-color:#38bdf880}.border-sky-400\/70{border-color:#38bdf8b3}.border-slate-200\/30{border-color:#e2e8f04d}.border-slate-400{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.border-slate-400\/60{border-color:#94a3b899}.border-slate-500\/70{border-color:#64748bb3}.border-slate-600{--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}.border-slate-600\/40{border-color:#47556966}.border-slate-600\/60{border-color:#47556999}.border-slate-600\/70{border-color:#475569b3}.border-slate-600\/80{border-color:#475569cc}.border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.border-slate-700\/60{border-color:#33415599}.border-slate-700\/70{border-color:#334155b3}.border-slate-700\/80{border-color:#334155cc}.border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.border-slate-800\/60{border-color:#1e293b99}.border-slate-800\/80{border-color:#1e293bcc}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.border-white\/10{border-color:#ffffff1a}.border-white\/40{border-color:#fff6}.border-white\/80{border-color:#fffc}.border-l-neutral-600{--tw-border-opacity: 1;border-left-color:rgb(82 82 82 / var(--tw-border-opacity))}.\!bg-black{--tw-bg-opacity: 1 !important;background-color:rgb(0 0 0 / var(--tw-bg-opacity))!important}.bg-amber-100\/95{background-color:#fef3c7f2}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity))}.bg-amber-400\/80{background-color:#fbbf24cc}.bg-amber-500{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity))}.bg-amber-500\/30{background-color:#f59e0b4d}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity))}.bg-amber-600\/70{background-color:#d97706b3}.bg-amber-600\/80{background-color:#d97706cc}.bg-amber-700\/20{background-color:#b4530933}.bg-amber-700\/30{background-color:#b453094d}.bg-amber-700\/35{background-color:#b4530959}.bg-amber-700\/50{background-color:#b4530980}.bg-amber-700\/80{background-color:#b45309cc}.bg-amber-800{--tw-bg-opacity: 1;background-color:rgb(146 64 14 / var(--tw-bg-opacity))}.bg-amber-900{--tw-bg-opacity: 1;background-color:rgb(120 53 15 / var(--tw-bg-opacity))}.bg-amber-900\/40{background-color:#78350f66}.bg-amber-900\/60{background-color:#78350f99}.bg-amber-900\/90{background-color:#78350fe6}.bg-amber-950{--tw-bg-opacity: 1;background-color:rgb(69 26 3 / var(--tw-bg-opacity))}.bg-amber-950\/35{background-color:#451a0359}.bg-amber-950\/40{background-color:#451a0366}.bg-amber-950\/70{background-color:#451a03b3}.bg-amber-950\/75{background-color:#451a03bf}.bg-amber-950\/90{background-color:#451a03e6}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-black\/45{background-color:#00000073}.bg-black\/50{background-color:#00000080}.bg-black\/55{background-color:#0000008c}.bg-black\/60{background-color:#0009}.bg-black\/70{background-color:#000000b3}.bg-black\/75{background-color:#000000bf}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-black\/95{background-color:#000000f2}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-cyan-300{--tw-bg-opacity: 1;background-color:rgb(103 232 249 / var(--tw-bg-opacity))}.bg-cyan-300\/20{background-color:#67e8f933}.bg-cyan-500{--tw-bg-opacity: 1;background-color:rgb(6 182 212 / var(--tw-bg-opacity))}.bg-cyan-500\/80{background-color:#06b6d4cc}.bg-cyan-900{--tw-bg-opacity: 1;background-color:rgb(22 78 99 / var(--tw-bg-opacity))}.bg-cyan-900\/45{background-color:#164e6373}.bg-cyan-900\/90{background-color:#164e63e6}.bg-cyan-950\/60{background-color:#08334499}.bg-emerald-200{--tw-bg-opacity: 1;background-color:rgb(167 243 208 / var(--tw-bg-opacity))}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}.bg-emerald-500\/45{background-color:#10b98173}.bg-emerald-500\/80{background-color:#10b981cc}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity))}.bg-emerald-600\/70{background-color:#059669b3}.bg-emerald-600\/80{background-color:#059669cc}.bg-emerald-700{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity))}.bg-emerald-700\/20{background-color:#04785733}.bg-emerald-700\/60{background-color:#04785799}.bg-emerald-700\/70{background-color:#047857b3}.bg-emerald-800{--tw-bg-opacity: 1;background-color:rgb(6 95 70 / var(--tw-bg-opacity))}.bg-emerald-900{--tw-bg-opacity: 1;background-color:rgb(6 78 59 / var(--tw-bg-opacity))}.bg-emerald-900\/60{background-color:#064e3b99}.bg-emerald-900\/90{background-color:#064e3be6}.bg-emerald-950{--tw-bg-opacity: 1;background-color:rgb(2 44 34 / var(--tw-bg-opacity))}.bg-emerald-950\/50{background-color:#022c2280}.bg-emerald-950\/90{background-color:#022c22e6}.bg-fuchsia-200{--tw-bg-opacity: 1;background-color:rgb(245 208 254 / var(--tw-bg-opacity))}.bg-fuchsia-600{--tw-bg-opacity: 1;background-color:rgb(192 38 211 / var(--tw-bg-opacity))}.bg-fuchsia-700{--tw-bg-opacity: 1;background-color:rgb(162 28 175 / var(--tw-bg-opacity))}.bg-fuchsia-800{--tw-bg-opacity: 1;background-color:rgb(134 25 143 / var(--tw-bg-opacity))}.bg-fuchsia-900{--tw-bg-opacity: 1;background-color:rgb(112 26 117 / var(--tw-bg-opacity))}.bg-fuchsia-900\/45{background-color:#701a7573}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.bg-indigo-600\/70{background-color:#4f46e5b3}.bg-indigo-900{--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}.bg-indigo-900\/60{background-color:#312e8199}.bg-indigo-950{--tw-bg-opacity: 1;background-color:rgb(30 27 75 / var(--tw-bg-opacity))}.bg-indigo-950\/60{background-color:#1e1b4b99}.bg-indigo-950\/90{background-color:#1e1b4be6}.bg-neutral-600{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity))}.bg-neutral-600\/80{background-color:#525252cc}.bg-neutral-700{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity))}.bg-neutral-700\/90{background-color:#404040e6}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity))}.bg-neutral-800\/80{background-color:#262626cc}.bg-neutral-900{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity))}.bg-neutral-900\/100{background-color:#171717}.bg-neutral-900\/60{background-color:#17171799}.bg-neutral-900\/70{background-color:#171717b3}.bg-neutral-900\/95{background-color:#171717f2}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity))}.bg-neutral-950\/90{background-color:#0a0a0ae6}.bg-pink-500{--tw-bg-opacity: 1;background-color:rgb(236 72 153 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-red-500\/45{background-color:#ef444473}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.bg-red-600\/70{background-color:#dc2626b3}.bg-red-700{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity))}.bg-red-700\/20{background-color:#b91c1c33}.bg-red-700\/60{background-color:#b91c1c99}.bg-red-700\/80{background-color:#b91c1ccc}.bg-red-800{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity))}.bg-red-900\/35{background-color:#7f1d1d59}.bg-red-900\/40{background-color:#7f1d1d66}.bg-red-900\/45{background-color:#7f1d1d73}.bg-red-900\/80{background-color:#7f1d1dcc}.bg-red-950{--tw-bg-opacity: 1;background-color:rgb(69 10 10 / var(--tw-bg-opacity))}.bg-red-950\/70{background-color:#450a0ab3}.bg-red-950\/90{background-color:#450a0ae6}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity))}.bg-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity))}.bg-sky-500{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.bg-sky-600{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity))}.bg-sky-700{--tw-bg-opacity: 1;background-color:rgb(3 105 161 / var(--tw-bg-opacity))}.bg-sky-700\/20{background-color:#0369a133}.bg-sky-700\/70{background-color:#0369a1b3}.bg-sky-900{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity))}.bg-sky-950{--tw-bg-opacity: 1;background-color:rgb(8 47 73 / var(--tw-bg-opacity))}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity))}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}.bg-slate-600\/70{background-color:#475569b3}.bg-slate-700{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.bg-slate-700\/30{background-color:#3341554d}.bg-slate-700\/60{background-color:#33415599}.bg-slate-700\/70{background-color:#334155b3}.bg-slate-700\/80{background-color:#334155cc}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-slate-800\/60{background-color:#1e293b99}.bg-slate-800\/70{background-color:#1e293bb3}.bg-slate-800\/80{background-color:#1e293bcc}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.bg-slate-900\/35{background-color:#0f172a59}.bg-slate-900\/40{background-color:#0f172a66}.bg-slate-900\/70{background-color:#0f172ab3}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity))}.bg-slate-950\/35{background-color:#02061759}.bg-slate-950\/80{background-color:#020617cc}.bg-slate-950\/85{background-color:#020617d9}.bg-slate-950\/90{background-color:#020617e6}.bg-slate-950\/95{background-color:#020617f2}.bg-transparent{background-color:transparent}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-white\/10{background-color:#ffffff1a}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.bg-zinc-900{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity))}.bg-zinc-900\/90{background-color:#18181be6}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-lime-800{--tw-gradient-from: #3f6212 var(--tw-gradient-from-position);--tw-gradient-to: rgb(63 98 18 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-neutral-800{--tw-gradient-from: #262626 var(--tw-gradient-from-position);--tw-gradient-to: rgb(38 38 38 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-neutral-700{--tw-gradient-to: rgb(64 64 64 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #404040 var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-neutral-600{--tw-gradient-to: #525252 var(--tw-gradient-to-position)}.to-teal-800{--tw-gradient-to: #115e59 var(--tw-gradient-to-position)}.bg-cover{background-size:cover}.bg-center{background-position:center}.bg-no-repeat{background-repeat:no-repeat}.fill-slate-100{fill:#f1f5f9}.fill-slate-200{fill:#e2e8f0}.fill-slate-400{fill:#94a3b8}.fill-white{fill:#fff}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[0\.85vw\]{padding:.85vw}.px-0{padding-left:0;padding-right:0}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.px-\[1\.2vw\]{padding-left:1.2vw;padding-right:1.2vw}.px-\[1\.6vw\]{padding-left:1.6vw;padding-right:1.6vw}.px-\[1vw\]{padding-left:1vw;padding-right:1vw}.px-\[3vw\]{padding-left:3vw;padding-right:3vw}.px-\[4vw\]{padding-left:4vw;padding-right:4vw}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-7{padding-top:1.75rem;padding-bottom:1.75rem}.py-\[0\.55vh\]{padding-top:.55vh;padding-bottom:.55vh}.py-\[1px\]{padding-top:1px;padding-bottom:1px}.py-\[2\.5vh\]{padding-top:2.5vh;padding-bottom:2.5vh}.py-\[2px\]{padding-top:2px;padding-bottom:2px}.py-\[4vh\]{padding-top:4vh;padding-bottom:4vh}.pb-0{padding-bottom:0}.pb-0\.5{padding-bottom:.125rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pr-0{padding-right:0}.pr-1{padding-right:.25rem}.pt-0\.5{padding-top:.125rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.45rem\]{font-size:.45rem}.text-\[0\.48rem\]{font-size:.48rem}.text-\[0\.52rem\]{font-size:.52rem}.text-\[0\.55em\]{font-size:.55em}.text-\[0\.55rem\]{font-size:.55rem}.text-\[0\.58rem\]{font-size:.58rem}.text-\[0\.5rem\]{font-size:.5rem}.text-\[0\.62rem\]{font-size:.62rem}.text-\[0\.65em\]{font-size:.65em}.text-\[0\.65rem\]{font-size:.65rem}.text-\[0\.68rem\]{font-size:.68rem}.text-\[0\.6rem\]{font-size:.6rem}.text-\[0\.72rem\]{font-size:.72rem}.text-\[0\.75rem\]{font-size:.75rem}.text-\[0\.78rem\]{font-size:.78rem}.text-\[0\.7rem\]{font-size:.7rem}.text-\[0\.82rem\]{font-size:.82rem}.text-\[0\.85rem\]{font-size:.85rem}.text-\[0\.8rem\]{font-size:.8rem}.text-\[1\.1rem\]{font-size:1.1rem}.text-\[1\.5rem\]{font-size:1.5rem}.text-\[1\.75rem\]{font-size:1.75rem}.text-\[17vw\]{font-size:17vw}.text-\[1rem\]{font-size:1rem}.text-\[2\.5vw\]{font-size:2.5vw}.text-\[2\.6vw\]{font-size:2.6vw}.text-\[3\.1vw\]{font-size:3.1vw}.text-\[3\.375rem\]{font-size:3.375rem}.text-\[3vw\]{font-size:3vw}.text-\[4vw\]{font-size:4vw}.text-\[6vw\]{font-size:6vw}.text-\[7vw\]{font-size:7vw}.text-\[clamp\(1\.25rem\,2\.5vh\,2\.6rem\)\]{font-size:clamp(1.25rem,2.5vh,2.6rem)}.text-\[clamp\(1\.2rem\,2\.9vh\,3rem\)\]{font-size:clamp(1.2rem,2.9vh,3rem)}.text-\[clamp\(2\.1rem\,5\.1vh\,5\.6rem\)\]{font-size:clamp(2.1rem,5.1vh,5.6rem)}.text-\[clamp\(2\.5rem\,5\.3vh\,5\.8rem\)\]{font-size:clamp(2.5rem,5.3vh,5.8rem)}.text-\[clamp\(2\.5rem\,7vh\,7rem\)\]{font-size:clamp(2.5rem,7vh,7rem)}.text-\[clamp\(2\.5rem\,8vh\,8rem\)\]{font-size:clamp(2.5rem,8vh,8rem)}.text-\[clamp\(2rem\,5vh\,5\.2rem\)\]{font-size:clamp(2rem,5vh,5.2rem)}.text-\[clamp\(3\.2rem\,11vh\,11rem\)\]{font-size:clamp(3.2rem,11vh,11rem)}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-\[0\.95\]{line-height:.95}.leading-none{line-height:1}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-normal{letter-spacing:0em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity))}.text-amber-200\/80{color:#fde68acc}.text-amber-200\/85{color:#fde68ad9}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity))}.text-amber-50{--tw-text-opacity: 1;color:rgb(255 251 235 / var(--tw-text-opacity))}.text-amber-50\/90{color:#fffbebe6}.text-amber-950{--tw-text-opacity: 1;color:rgb(69 26 3 / var(--tw-text-opacity))}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.text-cyan-50{--tw-text-opacity: 1;color:rgb(236 254 255 / var(--tw-text-opacity))}.text-cyan-950{--tw-text-opacity: 1;color:rgb(8 51 68 / var(--tw-text-opacity))}.text-emerald-100{--tw-text-opacity: 1;color:rgb(209 250 229 / var(--tw-text-opacity))}.text-emerald-100\/80{color:#d1fae5cc}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity))}.text-emerald-50{--tw-text-opacity: 1;color:rgb(236 253 245 / var(--tw-text-opacity))}.text-emerald-50\/90{color:#ecfdf5e6}.text-emerald-950{--tw-text-opacity: 1;color:rgb(2 44 34 / var(--tw-text-opacity))}.text-fuchsia-50{--tw-text-opacity: 1;color:rgb(253 244 255 / var(--tw-text-opacity))}.text-fuchsia-900{--tw-text-opacity: 1;color:rgb(112 26 117 / var(--tw-text-opacity))}.text-indigo-200{--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}.text-indigo-50{--tw-text-opacity: 1;color:rgb(238 242 255 / var(--tw-text-opacity))}.text-indigo-50\/90{color:#eef2ffe6}.text-lime-300{--tw-text-opacity: 1;color:rgb(190 242 100 / var(--tw-text-opacity))}.text-lime-400{--tw-text-opacity: 1;color:rgb(163 230 53 / var(--tw-text-opacity))}.text-neutral-100{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity))}.text-neutral-200{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity))}.text-neutral-300{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity))}.text-neutral-400{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity))}.text-neutral-50{--tw-text-opacity: 1;color:rgb(250 250 250 / var(--tw-text-opacity))}.text-neutral-500{--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity))}.text-red-100{--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity))}.text-red-100\/90{color:#fee2e2e6}.text-red-100\/95{color:#fee2e2f2}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-50{--tw-text-opacity: 1;color:rgb(254 242 242 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity))}.text-sky-100{--tw-text-opacity: 1;color:rgb(224 242 254 / var(--tw-text-opacity))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity))}.text-sky-300{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity))}.text-sky-50{--tw-text-opacity: 1;color:rgb(240 249 255 / var(--tw-text-opacity))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-400\/60{color:#94a3b899}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-slate-950{--tw-text-opacity: 1;color:rgb(2 6 23 / var(--tw-text-opacity))}.text-teal-400{--tw-text-opacity: 1;color:rgb(45 212 191 / var(--tw-text-opacity))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-white\/75{color:#ffffffbf}.text-white\/90{color:#ffffffe6}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity))}.accent-amber-500{accent-color:#f59e0b}.accent-cyan-500{accent-color:#06b6d4}.accent-emerald-400{accent-color:#34d399}.accent-emerald-500{accent-color:#10b981}.accent-red-500{accent-color:#ef4444}.accent-sky-500{accent-color:#0ea5e9}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_1px_0_rgba\(255\,255\,255\,0\.05\)_inset\,0_10px_24px_rgba\(0\,0\,0\,0\.28\)\]{--tw-shadow: 0 1px 0 rgba(255,255,255,.05) inset,0 10px 24px rgba(0,0,0,.28);--tw-shadow-colored: inset 0 1px 0 var(--tw-shadow-color), 0 10px 24px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color: rgb(0 0 0 / .4);--tw-shadow: var(--tw-shadow-colored)}.shadow-cyan-950\/50{--tw-shadow-color: rgb(8 51 68 / .5);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-8{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-amber-200\/60{--tw-ring-color: rgb(253 230 138 / .6)}.ring-amber-300{--tw-ring-opacity: 1;--tw-ring-color: rgb(252 211 77 / var(--tw-ring-opacity))}.ring-amber-300\/80{--tw-ring-color: rgb(252 211 77 / .8)}.ring-cyan-200\/90{--tw-ring-color: rgb(165 243 252 / .9)}.ring-emerald-300\/50{--tw-ring-color: rgb(110 231 183 / .5)}.ring-emerald-300\/70{--tw-ring-color: rgb(110 231 183 / .7)}.ring-emerald-300\/80{--tw-ring-color: rgb(110 231 183 / .8)}.ring-indigo-300\/70{--tw-ring-color: rgb(165 180 252 / .7)}.ring-red-300\/60{--tw-ring-color: rgb(252 165 165 / .6)}.ring-red-300\/80{--tw-ring-color: rgb(252 165 165 / .8)}.ring-red-500\/70{--tw-ring-color: rgb(239 68 68 / .7)}.ring-red-500\/80{--tw-ring-color: rgb(239 68 68 / .8)}.ring-red-500\/90{--tw-ring-color: rgb(239 68 68 / .9)}.ring-sky-300\/50{--tw-ring-color: rgb(125 211 252 / .5)}.ring-sky-400\/80{--tw-ring-color: rgb(56 189 248 / .8)}.ring-slate-400\/40{--tw-ring-color: rgb(148 163 184 / .4)}.ring-slate-400\/70{--tw-ring-color: rgb(148 163 184 / .7)}.ring-slate-600{--tw-ring-opacity: 1;--tw-ring-color: rgb(71 85 105 / var(--tw-ring-opacity))}.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)}.grayscale{--tw-grayscale: grayscale(100%);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)}.invert{--tw-invert: invert(100%);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)}.sepia{--tw-sepia: sepia(100%);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(4px);-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{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\,border-radius\,background-color\]{transition-property:width,height,border-radius,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\]{transition-property:width,height;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,opacity\]{transition-property:width,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-700{transition-duration:.7s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\@container{container-type:inline-size}.\[clip-path\:polygon\(0_0\,0_100\%\,100\%_100\%\)\]{clip-path:polygon(0 0,0 100%,100% 100%)}.\[clip-path\:polygon\(0_0\,100\%_0\,0_100\%\)\]{clip-path:polygon(0 0,100% 0,0 100%)}.\[clip-path\:polygon\(0_0\,100\%_0\,100\%_100\%\)\]{clip-path:polygon(0 0,100% 0,100% 100%)}.\[clip-path\:polygon\(100\%_0\,0_100\%\,100\%_100\%\)\]{clip-path:polygon(100% 0,0 100%,100% 100%)}.\[writing-mode\:vertical-rl\]{writing-mode:vertical-rl}:root{font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}body{margin:0;min-height:100vh;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}html,body,*{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar,*::-webkit-scrollbar{display:none;width:0;height:0}.placeholder\:text-slate-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.last\:mb-0:last-child{margin-bottom:0}.last\:border-0:last-child{border-width:0px}.hover\:-translate-y-0\.5:hover{--tw-translate-y: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-green-100:hover{--tw-border-opacity: 1;border-color:rgb(220 252 231 / var(--tw-border-opacity))}.hover\:border-rose-500:hover{--tw-border-opacity: 1;border-color:rgb(244 63 94 / var(--tw-border-opacity))}.hover\:border-sky-500:hover{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity))}.hover\:border-white:hover{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.hover\:border-white\/60:hover{border-color:#fff9}.hover\:bg-amber-400:hover{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity))}.hover\:bg-amber-600:hover{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity))}.hover\:bg-amber-800:hover{--tw-bg-opacity: 1;background-color:rgb(146 64 14 / var(--tw-bg-opacity))}.hover\:bg-amber-900:hover{--tw-bg-opacity: 1;background-color:rgb(120 53 15 / var(--tw-bg-opacity))}.hover\:bg-amber-900\/95:hover{background-color:#78350ff2}.hover\:bg-black:hover{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.hover\:bg-black\/80:hover{background-color:#000c}.hover\:bg-cyan-400:hover{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity))}.hover\:bg-cyan-800:hover{--tw-bg-opacity: 1;background-color:rgb(21 94 117 / var(--tw-bg-opacity))}.hover\:bg-emerald-400:hover{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity))}.hover\:bg-emerald-500:hover{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}.hover\:bg-emerald-700:hover{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity))}.hover\:bg-emerald-900\/95:hover{background-color:#064e3bf2}.hover\:bg-fuchsia-500:hover{--tw-bg-opacity: 1;background-color:rgb(217 70 239 / var(--tw-bg-opacity))}.hover\:bg-green-400:hover{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity))}.hover\:bg-indigo-500:hover{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.hover\:bg-indigo-800:hover{--tw-bg-opacity: 1;background-color:rgb(55 48 163 / var(--tw-bg-opacity))}.hover\:bg-indigo-900:hover{--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}.hover\:bg-neutral-700:hover{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity))}.hover\:bg-pink-400:hover{--tw-bg-opacity: 1;background-color:rgb(244 114 182 / var(--tw-bg-opacity))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.hover\:bg-red-900:hover{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity))}.hover\:bg-rose-500:hover{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.hover\:bg-sky-500:hover{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.hover\:bg-slate-600:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}.hover\:bg-slate-700:hover{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.hover\:bg-slate-700\/70:hover{background-color:#334155b3}.hover\:bg-slate-900:hover{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-zinc-900:hover{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-xl:hover{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);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)}.focus\:bg-white\/10:focus{background-color:#ffffff1a}.focus\:text-white:focus{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-emerald-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(16 185 129 / var(--tw-ring-opacity))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline:focus-visible{outline-style:solid}.focus-visible\:outline-1:focus-visible{outline-width:1px}.focus-visible\:outline-offset-1:focus-visible{outline-offset:1px}.focus-visible\:outline-slate-500:focus-visible{outline-color:#64748b}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-4:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-amber-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity))}.focus-visible\:ring-amber-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(252 211 77 / var(--tw-ring-opacity))}.focus-visible\:ring-cyan-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(165 243 252 / var(--tw-ring-opacity))}.focus-visible\:ring-cyan-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(103 232 249 / var(--tw-ring-opacity))}.focus-visible\:ring-emerald-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(167 243 208 / var(--tw-ring-opacity))}.focus-visible\:ring-emerald-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(110 231 183 / var(--tw-ring-opacity))}.focus-visible\:ring-indigo-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity))}.focus-visible\:ring-indigo-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(165 180 252 / var(--tw-ring-opacity))}.focus-visible\:ring-red-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity))}.focus-visible\:ring-sky-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(186 230 253 / var(--tw-ring-opacity))}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.99\]:active{--tw-scale-x: .99;--tw-scale-y: .99;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:brightness-125:active{--tw-brightness: brightness(1.25);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)}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:cursor-wait:disabled{cursor:wait}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-45:disabled{opacity:.45}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.disabled\:opacity-70:disabled{opacity:.7}.disabled\:opacity-75:disabled{opacity:.75}@container (min-width: 20rem){.\@xs\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@container (min-width: 24rem){.\@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\:grid-cols-\[minmax\(0\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr)}.\@sm\:grid-cols-\[minmax\(0\,1\.4fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr)}.\@sm\:flex-row{flex-direction:row}.\@sm\:flex-col{flex-direction:column}.\@sm\:gap-0{gap:0px}.\@sm\:overflow-hidden{overflow:hidden}.\@sm\:whitespace-nowrap{white-space:nowrap}.\@sm\:border-b-0{border-bottom-width:0px}.\@sm\:border-r{border-right-width:1px}.\@sm\:py-0{padding-top:0;padding-bottom:0}.\@sm\:text-\[0\.55em\]{font-size:.55em}}@container (min-width: 28rem){.\@md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\@\[28rem\]\:col-span-2{grid-column:span 2 / span 2}.\@\[28rem\]\:grid{display:grid}.\@\[28rem\]\:h-32{height:8rem}.\@\[28rem\]\:min-w-0{min-width:0px}.\@\[28rem\]\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-\[minmax\(0\,1\.15fr\)_minmax\(0\,0\.85fr\)_minmax\(0\,0\.85fr\)\]{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr) minmax(0,.85fr)}.\@\[28rem\]\:grid-cols-\[minmax\(0\,1fr\)_9rem_10rem\]{grid-template-columns:minmax(0,1fr) 9rem 10rem}.\@\[28rem\]\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.\@\[28rem\]\:text-right{text-align:right}}@container (min-width: 32rem){.\@lg\:w-auto{width:auto}.\@lg\:min-w-24{min-width:6rem}.\@lg\:flex-row{flex-direction:row}.\@lg\:items-center{align-items:center}.\@lg\:justify-between{justify-content:space-between}.\@\[32rem\]\:col-span-2{grid-column:span 2 / span 2}.\@\[32rem\]\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\@\[32rem\]\:grid-cols-\[minmax\(0\,1\.25fr\)_minmax\(0\,0\.9fr\)\]{grid-template-columns:minmax(0,1.25fr) minmax(0,.9fr)}}@media(prefers-reduced-motion:reduce){.motion-reduce\:transition-none{transition-property:none}}@media(max-width:520px){.max-\[520px\]\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-\[520px\]\:justify-start{justify-content:flex-start}}@media(max-width:420px){.max-\[420px\]\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-\[420px\]\:justify-start{justify-content:flex-start}}@media(min-width:640px){.sm\:block{display:block}.sm\:min-h-\[18rem\]{min-height:18rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media(min-width:768px){.md\:h-full{height:100%}.md\:h-screen{height:100vh}.md\:min-h-0{min-height:0px}.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-\[0\.9fr_1fr_1\.3fr\]{grid-template-columns:.9fr 1fr 1.3fr}.md\:grid-cols-\[minmax\(0\,1\.5fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr)}.md\:grid-cols-\[minmax\(0\,1fr\)_10rem\]{grid-template-columns:minmax(0,1fr) 10rem}.md\:grid-cols-\[minmax\(0\,1fr\)_18rem\]{grid-template-columns:minmax(0,1fr) 18rem}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,0\.7fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,.7fr)}.md\:grid-cols-\[minmax\(16rem\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(16rem,.8fr) minmax(0,1.2fr)}.md\:items-start{align-items:flex-start}.md\:overflow-hidden{overflow:hidden}.md\:overflow-y-auto{overflow-y:auto}.md\:text-lg{font-size:1.125rem;line-height:1.75rem}.md\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media(min-width:1024px){.lg\:grid-cols-\[24rem_minmax\(0\,1fr\)\]{grid-template-columns:24rem minmax(0,1fr)}.lg\:grid-cols-\[minmax\(0\,1fr\)_20rem\]{grid-template-columns:minmax(0,1fr) 20rem}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}}@media(min-width:1280px){.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}}.\[\&_img\]\:object-contain img{-o-object-fit:contain;object-fit:contain}.\[\&_video\]\:object-contain video{-o-object-fit:contain;object-fit:contain}.page-theme{background-color:#050505;background-attachment:fixed}.page-theme-preview{background-attachment:scroll;background-position:center}.page-theme-black{background-color:#000;background-image:none}.page-theme-green-mode{background-color:#0f0;background-image:none}.page-theme-pride-mix{background-image:repeating-linear-gradient(45deg,#5bcefa 0 48px,#f5a9b8 48px 96px,#fff 96px 144px,#f5a9b8 144px 192px,#5bcefa 192px 240px,#e40303 240px 288px,#ff8c00 288px 336px,#ffed00 336px 384px,#00a651 384px 432px,#06f 432px 480px,#9b2fae 480px 528px)}.page-theme-rainbow{background-image:repeating-linear-gradient(45deg,#e40303 0 48px,#ff8c00 48px 96px,#ffed00 96px 144px,#008026 144px 192px,#004dff 192px 240px,#750787 240px 288px)}.page-theme-progress-pride{background-image:repeating-linear-gradient(45deg,#000 0 48px,#784f17 48px 96px,#5bcefa 96px 144px,#f5a9b8 144px 192px,#fff 192px 240px,#e40303 240px 288px,#ff8c00 288px 336px,#ffed00 336px 384px,#008026 384px 432px,#004dff 432px 480px,#750787 480px 528px)}.page-theme-transgender{background-image:repeating-linear-gradient(45deg,#5bcefa 0 48px,#f5a9b8 48px 96px,#fff 96px 144px,#f5a9b8 144px 192px,#5bcefa 192px 240px)}.page-theme-bisexual{background-image:repeating-linear-gradient(45deg,#d60270 0 48px,#9b4f96 48px 96px,#0038a8 96px 144px)}.page-theme-lesbian{background-image:repeating-linear-gradient(45deg,#d52d00 0 48px,#ef7627 48px 96px,#ff9a56 96px 144px,#fff 144px 192px,#d162a4 192px 240px,#b55690 240px 288px,#a30262 288px 336px)}.page-theme-nonbinary{background-image:repeating-linear-gradient(45deg,#fff430 0 48px,#fff 48px 96px,#9c59d1 96px 144px,#2d2d2d 144px 192px)}.page-theme-pansexual{background-image:repeating-linear-gradient(45deg,#ff218c 0 48px,#ffd800 48px 96px,#21b1ff 96px 144px)}.page-theme-asexual{background-image:repeating-linear-gradient(45deg,#111 0 48px,#a3a3a3 48px 96px,#fff 96px 144px,purple 144px 192px)}.page-theme-aurora{background-color:#03120f;background-image:radial-gradient(circle at 18% 24%,rgba(45,212,191,.95),transparent 34%),radial-gradient(circle at 76% 18%,rgba(168,85,247,.9),transparent 38%),radial-gradient(circle at 56% 82%,rgba(34,197,94,.88),transparent 42%),linear-gradient(125deg,#020617,#0f172a 45%,#082f49);background-size:34rem 28rem,38rem 32rem,42rem 36rem,auto}.page-theme-synthwave-grid{background-color:#090018;background-image:linear-gradient(rgba(34,211,238,.7) 2px,transparent 2px),linear-gradient(90deg,rgba(236,72,153,.72) 2px,transparent 2px),radial-gradient(ellipse at 50% 100%,#7e22ce 0,#1e1b4b 38%,#090018 72%);background-size:54px 54px,54px 54px,auto}.page-theme-neon-checker{background-color:#111827;background-image:conic-gradient(from 45deg,#06b6d4 0 25%,#7c3aed 0 50%,#ec4899 0 75%,#111827 0);background-size:84px 84px}.page-theme-ocean-current{background-color:#082f49;background-image:repeating-linear-gradient(35deg,rgba(103,232,249,.72) 0 12px,transparent 12px 54px),repeating-linear-gradient(-35deg,rgba(14,116,144,.72) 0 21px,transparent 21px 72px),linear-gradient(90deg,#0c4a6e,#0891b2,#164e63)}.page-theme-ember-lattice{background-color:#1c0704;background-image:repeating-linear-gradient(45deg,transparent 0 36px,rgba(251,146,60,.9) 36px 45px),repeating-linear-gradient(-45deg,transparent 0 48px,rgba(239,68,68,.78) 48px 57px),radial-gradient(circle at center,#7c2d12,#1c0704 70%)}.page-theme-starfield{background-color:#020617;background-image:radial-gradient(circle,rgba(255,255,255,.95) 0 2px,transparent 3px),radial-gradient(circle,rgba(125,211,252,.9) 0 3px,transparent 4px),radial-gradient(circle,rgba(216,180,254,.8) 0 2px,transparent 3px),linear-gradient(135deg,#020617,#172554 55%,#3b0764);background-position:0 0,57px 81px,123px 33px,0 0;background-size:111px 111px,183px 183px,237px 237px,auto}.page-theme-vaporwave-sunset{background-color:#2e1065;background-image:repeating-linear-gradient(0deg,transparent 0 24px,rgba(103,232,249,.48) 24px 27px,transparent 27px 54px),radial-gradient(circle at 50% 78%,#fda4af 0 14%,#f97316 14% 25%,transparent 25.5%),linear-gradient(180deg,#172554,#6b21a8 48%,#db2777 72%,#f97316);background-size:auto,48rem 34rem,auto}.page-theme-electric-circuit{background-color:#020617;background-image:repeating-linear-gradient(90deg,transparent 0 48px,rgba(34,211,238,.9) 48px 52px,transparent 52px 96px),repeating-linear-gradient(0deg,transparent 0 68px,rgba(168,85,247,.86) 68px 72px,transparent 72px 124px),radial-gradient(circle at center,#312e81 0,#0f172a 45%,#020617 78%)}.page-theme-lava-flow{background-color:#180503;background-image:radial-gradient(ellipse at 18% 28%,#facc15 0 8%,#f97316 9% 18%,#991b1b 24%,transparent 39%),radial-gradient(ellipse at 76% 64%,#fb923c 0 10%,#dc2626 18%,#7f1d1d 29%,transparent 44%),radial-gradient(ellipse at 48% 92%,#fde047 0 7%,#ea580c 17%,transparent 36%),linear-gradient(135deg,#180503,#450a0a 50%,#1c0704);background-size:42rem 34rem,48rem 39rem,37rem 31rem,auto}.page-theme-deep-space-nebula{background-color:#020617;background-image:radial-gradient(ellipse at 22% 38%,rgba(217,70,239,.92),transparent 34%),radial-gradient(ellipse at 72% 24%,rgba(59,130,246,.9),transparent 38%),radial-gradient(ellipse at 58% 82%,rgba(14,165,233,.72),transparent 36%),radial-gradient(circle,rgba(255,255,255,.9) 0 2px,transparent 3px),linear-gradient(125deg,#020617,#1e1b4b 52%,#3b0764);background-position:center,center,center,17px 31px,center;background-size:42rem 34rem,48rem 38rem,39rem 35rem,173px 173px,auto}.page-theme-holographic-waves{background-color:#164e63;background-image:repeating-linear-gradient(32deg,rgba(255,255,255,.4) 0 14px,transparent 14px 82px),repeating-linear-gradient(-38deg,rgba(216,180,254,.38) 0 18px,transparent 18px 104px),linear-gradient(110deg,#67e8f9,#c4b5fd 32%,#f9a8d4 60%,#86efac)}.page-theme-hazard-stripes{background-color:#111;background-image:repeating-linear-gradient(45deg,#facc15 0 48px,#171717 48px 96px)}.page-theme-mint-mosaic{background-color:#064e3b;background-image:conic-gradient(from 45deg,#a7f3d0 0 25%,#0f766e 0 50%,#fef3c7 0 75%,#164e63 0);background-size:96px 96px}.page-theme-candy-swirl{background-color:#fdf2f8;background-image:repeating-radial-gradient(circle at 28% 34%,#f9a8d4 0 28px,#fef3c7 28px 56px,#93c5fd 56px 84px,#c4b5fd 84px 112px,#f9a8d4 112px 140px)}.inter-instance-overlay-frame{max-width:calc(100vw - .5rem)}.inter-instance-overlay-body{max-height:82vh}@media(min-width:1024px){.inter-instance-overlay-scale{zoom:1.5}.inter-instance-overlay-frame{max-width:calc((100vw - .5rem) / 1.5)}.inter-instance-overlay-body{max-height:54.6667vh}}.chat-composer{container-type:inline-size;display:flex;flex-wrap:wrap;align-items:stretch;gap:.125rem;min-width:0;overflow:hidden}.chat-composer-nickname{flex:1 1 100%;min-width:0;order:1}.chat-composer-input{flex:1 1 0%;min-width:0;order:2}.chat-composer-send{flex:0 0 auto;align-self:stretch;order:3}.chat-composer-tts{display:flex;flex:1 1 100%;align-items:center;gap:.125rem;min-width:0;overflow:hidden;order:4}@container (min-width: 44rem){.chat-composer{flex-wrap:nowrap}.chat-composer-nickname{flex:0 0 7rem;order:1}.chat-composer-input{order:2}.chat-composer-tts{flex:0 1 auto;order:3}.chat-composer-send{order:4}}.mobile-text-entry{font-size:16px;-webkit-text-size-adjust:100%;touch-action:manipulation}.no-touch-select{-moz-user-select:none;user-select:none;-webkit-user-select:none;-ms-user-select:none;-webkit-touch-callout:none}.mobile-touch-control{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;overscroll-behavior:contain}.mobile-drag-control{touch-action:none}.newgen-wheel-drop{pointer-events:none;position:absolute;z-index:20;top:50%;display:flex;width:4rem;height:15rem;align-items:center;justify-content:center;border-radius:.25rem;background:#ef44445c;color:#fff;transform:translateY(-50%)}.newgen-wheel-drop--left{left:1rem}.newgen-wheel-drop--right{right:1rem}.newgen-wheel-drop span{white-space:nowrap;font-size:.9rem;font-weight:700;letter-spacing:.08em;line-height:1;text-shadow:0 1px 2px rgb(0 0 0 / .65)}.newgen-wheel-drop--left span{transform:rotate(-90deg)}.newgen-wheel-drop--right span{transform:rotate(90deg)}.newgen-bumper-path{fill:none;stroke:#ef44447a;stroke-width:26;stroke-linecap:round;vector-effect:non-scaling-stroke}.newgen-bumper-label{fill:#fff;font-family:system-ui,sans-serif;font-size:20px;font-weight:700;letter-spacing:1px;paint-order:stroke;stroke:#000000a6;stroke-width:1.5px}.newgen-ir-proximity-cone{fill:none;stroke-width:6;stroke-linecap:round;vector-effect:non-scaling-stroke;transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.newgen-ir-proximity-cone{transition:none}}.newgen-cliff-path{fill:none;stroke:#f59e0bad;stroke-width:22;stroke-linecap:round;vector-effect:non-scaling-stroke}.newgen-cliff-label{fill:#fff;font-family:system-ui,sans-serif;font-size:15px;font-weight:700;letter-spacing:1px;paint-order:stroke;stroke:#000000a6;stroke-width:1.5px}.newgen-bottom-sensor-hud{pointer-events:none;position:absolute;z-index:20;right:0;bottom:-2.5rem;left:0;width:100%;height:clamp(7rem,18vh,11rem);overflow:visible}@keyframes batteryTickWarn{0%,49%{background-color:#fffffff2}50%,to{background-color:#ef4444f2}}.battery-tick-warn{animation:batteryTickWarn .4s steps(2,end) infinite}@keyframes batteryUrgentFlash{0%,49%{opacity:1}50%,to{opacity:.25}}.battery-urgent-flash{animation:batteryUrgentFlash .4s steps(2,end) infinite}@media(max-width:1023px){.driver-mobile-layout{--mobile-landscape-video-width: min(60vw, 133.333svh)}.driver-mobile-layout:has(>.panel-section){--mobile-landscape-video-width: min(60vw, calc(133.333svh - 3.333rem) )}.driver-mobile-layout .mobile-landscape-driver-grid{grid-template-columns:minmax(0,1fr) minmax(0,var(--mobile-landscape-video-width)) minmax(0,1fr)}html:has(.driver-mobile-layout){scroll-snap-type:y mandatory}.driver-mobile-layout .mobile-top-snap{position:absolute;top:0;left:0;width:1px;height:1px;pointer-events:none;scroll-snap-align:start;scroll-snap-stop:always}.driver-mobile-layout .mobile-content-snap,.driver-mobile-layout .mobile-tabs-snap{scroll-snap-align:start;scroll-snap-stop:always}}@keyframes initial-session-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes initial-session-spinner-hue{0%{filter:hue-rotate(0deg)}to{filter:hue-rotate(360deg)}}@media(prefers-reduced-motion:no-preference){.initial-session-spinner-rotation{animation:initial-session-spinner-rotate 1.8s linear infinite}.initial-session-spinner-image{animation:initial-session-spinner-hue 2.8s linear infinite}} diff --git a/server/public/assets/index-BFNKIMjg.css b/server/public/assets/index-BFNKIMjg.css deleted file mode 100644 index beb9963c..00000000 --- a/server/public/assets/index-BFNKIMjg.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.\!panel,.panel{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));padding:0;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.panel-section{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity));padding:0;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.panel-muted{border-radius:.375rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.surface{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.surface-muted{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity))}.field-input{border-radius:.375rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity));padding:.125rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.field-input::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.field-input::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.field-input:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1;--tw-ring-color: rgb(14 165 233 / var(--tw-ring-opacity))}.button-dark{border-radius:.375rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(125 211 252 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity));padding:.125rem;font-size:.875rem;line-height:1.25rem;font-weight:500;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.button-dark:hover{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.button-danger{border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity));padding:.125rem;font-size:.875rem;line-height:1.25rem;font-weight:500;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.button-danger:hover{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{left:0;right:0}.inset-x-1{left:.25rem;right:.25rem}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-0\.5{bottom:.125rem}.bottom-1{bottom:.25rem}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.bottom-40{bottom:10rem}.bottom-\[8\.5rem\]{bottom:8.5rem}.bottom-\[calc\(100\%\+0\.125rem\)\]{bottom:calc(100% + .125rem)}.left-0{left:0}.left-0\.5{left:.125rem}.left-1{left:.25rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-4{left:1rem}.left-\[1\.979rem\]{left:1.979rem}.left-\[5\.338rem\]{left:5.338rem}.left-\[61\%\]{left:61%}.left-\[90\%\]{left:90%}.right-0{right:0}.right-0\.5{right:.125rem}.right-1{right:.25rem}.right-10{right:2.5rem}.right-2{right:.5rem}.right-\[8\.5rem\]{right:8.5rem}.top-0{top:0}.top-0\.5{top:.125rem}.top-1{top:.25rem}.top-1\/2{top:50%}.top-10{top:2.5rem}.top-6{top:1.5rem}.top-\[0\.662rem\]{top:.662rem}.top-\[4\.021rem\]{top:4.021rem}.top-\[58\%\]{top:58%}.top-\[61\%\]{top:61%}.top-\[8\.5rem\]{top:8.5rem}.top-\[90\%\]{top:90%}.top-full{top:100%}.z-0{z-index:0}.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-\[1000\]{z-index:1000}.z-\[100\]{z-index:100}.z-\[110\]{z-index:110}.z-\[120\]{z-index:120}.z-\[130\]{z-index:130}.z-\[2000\]{z-index:2000}.z-\[25\]{z-index:25}.z-\[55\]{z-index:55}.z-\[60\]{z-index:60}.z-\[70\]{z-index:70}.order-1{order:1}.order-2{order:2}.col-span-2{grid-column:span 2 / span 2}.col-span-full{grid-column:1 / -1}.row-span-2{grid-row:span 2 / span 2}.m-0{margin:0}.-mx-0\.5{margin-left:-.125rem;margin-right:-.125rem}.mx-auto{margin-left:auto;margin-right:auto}.my-\[-0\.125rem\]{margin-top:-.125rem;margin-bottom:-.125rem}.-mt-0\.5{margin-top:-.125rem}.mb-0\.5{margin-bottom:.125rem}.mb-2{margin-bottom:.5rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.mr-0{margin-right:0}.mr-0\.5{margin-right:.125rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-\[0\.35vh\]{margin-top:.35vh}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.\!block{display:block!important}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-\[4\/3\]{aspect-ratio:4/3}.aspect-square{aspect-ratio:1 / 1}.aspect-video{aspect-ratio:16 / 9}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-28{height:7rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-40{height:10rem}.h-44{height:11rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-52{height:13rem}.h-64{height:16rem}.h-8{height:2rem}.h-80{height:20rem}.h-\[100dvh\]{height:100dvh}.h-\[25\.5rem\]{height:25.5rem}.h-\[2px\]{height:2px}.h-\[3\.5rem\]{height:3.5rem}.h-\[70\%\]{height:70%}.h-\[7rem\]{height:7rem}.h-\[8\.5rem\]{height:8.5rem}.h-\[8vh\]{height:8vh}.h-\[calc\(100dvh-0\.25rem\)\]{height:calc(100dvh - .25rem)}.h-\[calc\(1rem\+0\.25rem\)\]{height:1.25rem}.h-\[min\(100svh\,32rem\)\]{height:min(100svh,32rem)}.h-\[min\(60svh\,24rem\)\]{height:min(60svh,24rem)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-24{max-height:6rem}.max-h-32{max-height:8rem}.max-h-36{max-height:9rem}.max-h-40{max-height:10rem}.max-h-52{max-height:13rem}.max-h-72{max-height:18rem}.max-h-80{max-height:20rem}.max-h-\[14rem\]{max-height:14rem}.max-h-\[36rem\]{max-height:36rem}.max-h-\[70vh\]{max-height:70vh}.max-h-\[72vh\]{max-height:72vh}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-full{max-height:100%}.min-h-0{min-height:0px}.min-h-10{min-height:2.5rem}.min-h-12{min-height:3rem}.min-h-14{min-height:3.5rem}.min-h-5{min-height:1.25rem}.min-h-56{min-height:14rem}.min-h-7{min-height:1.75rem}.min-h-9{min-height:2.25rem}.min-h-\[100dvh\]{min-height:100dvh}.min-h-\[10rem\]{min-height:10rem}.min-h-\[12rem\]{min-height:12rem}.min-h-\[14rem\]{min-height:14rem}.min-h-\[16rem\]{min-height:16rem}.min-h-\[18rem\]{min-height:18rem}.min-h-\[24rem\]{min-height:24rem}.min-h-\[3\.5rem\]{min-height:3.5rem}.min-h-\[4\.5rem\]{min-height:4.5rem}.min-h-\[4rem\]{min-height:4rem}.min-h-\[7rem\]{min-height:7rem}.min-h-\[calc\(100dvh-0\.25rem\)\]{min-height:calc(100dvh - .25rem)}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-0{width:0px}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/5{width:60%}.w-4{width:1rem}.w-4\/5{width:80%}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-80{width:20rem}.w-\[100vw\]{width:100vw}.w-\[12\.5rem\]{width:12.5rem}.w-\[12rem\]{width:12rem}.w-\[14rem\]{width:14rem}.w-\[20\.5rem\]{width:20.5rem}.w-\[22rem\]{width:22rem}.w-\[25\.5rem\]{width:25.5rem}.w-\[2px\]{width:2px}.w-\[3\.25rem\]{width:3.25rem}.w-\[4\.5rem\]{width:4.5rem}.w-\[5\.5rem\]{width:5.5rem}.w-\[5rem\]{width:5rem}.w-\[8\.5rem\]{width:8.5rem}.w-\[9rem\]{width:9rem}.w-\[calc\(1rem\+0\.25rem\)\]{width:1.25rem}.w-\[min\(20rem\,calc\(100vw-1rem\)\)\]{width:min(20rem,calc(100vw - 1rem))}.w-\[min\(30rem\,80\%\)\]{width:min(30rem,80%)}.w-\[min\(32rem\,80\%\)\]{width:min(32rem,80%)}.w-\[min\(32rem\,calc\(80vw-1\.25rem\)\)\]{width:min(32rem,calc(80vw - 1.25rem))}.w-\[min\(34vw\,34rem\)\]{width:min(34vw,34rem)}.w-\[min\(36rem\,82\%\)\]{width:min(36rem,82%)}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:0px}.min-w-28{min-width:7rem}.min-w-56{min-width:14rem}.min-w-\[0\]{min-width:0}.min-w-\[3\.7rem\]{min-width:3.7rem}.min-w-\[5\.5rem\]{min-width:5.5rem}.min-w-\[calc\(50\%_-_0\.125rem\)\]{min-width:calc(50% - .125rem)}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-40{max-width:10rem}.max-w-4xl{max-width:56rem}.max-w-64{max-width:16rem}.max-w-7xl{max-width:80rem}.max-w-\[120rem\]{max-width:120rem}.max-w-\[24rem\]{max-width:24rem}.max-w-\[40vw\]{max-width:40vw}.max-w-\[55\%\]{max-width:55%}.max-w-\[70\%\]{max-width:70%}.max-w-\[70vw\]{max-width:70vw}.max-w-\[80vw\]{max-width:80vw}.max-w-\[92\%\]{max-width:92%}.max-w-\[92vw\]{max-width:92vw}.max-w-\[94\%\]{max-width:94%}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-\[0\.72\]{flex:.72}.flex-\[1\.1\]{flex:1.1}.flex-\[1\.22\]{flex:1.22}.flex-\[1\.28\]{flex:1.28}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-\[0\.75\]{flex-grow:.75}.grow-\[0\.9\]{flex-grow:.9}.grow-\[1\]{flex-grow:1}.basis-0{flex-basis:0px}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-45{--tw-rotate: -45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-90{--tw-rotate: -90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-\[135deg\]{--tw-rotate: -135deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-45{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-\[135deg\]{--tw-rotate: 135deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.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-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[1\.5rem_minmax\(0\,1fr\)_3rem\]{grid-template-columns:1.5rem minmax(0,1fr) 3rem}.grid-cols-\[minmax\(0\,1\.2fr\)_repeat\(4\,minmax\(0\,1fr\)\)\]{grid-template-columns:minmax(0,1.2fr) repeat(4,minmax(0,1fr))}.grid-cols-\[minmax\(0\,1\.35fr\)_minmax\(0\,0\.95fr\)\]{grid-template-columns:minmax(0,1.35fr) minmax(0,.95fr)}.grid-cols-\[minmax\(0\,1\.3fr\)_minmax\(0\,0\.22fr\)\]{grid-template-columns:minmax(0,1.3fr) minmax(0,.22fr)}.grid-cols-\[minmax\(0\,1\.45fr\)_minmax\(0\,0\.85fr\)\]{grid-template-columns:minmax(0,1.45fr) minmax(0,.85fr)}.grid-cols-\[minmax\(0\,1\.6fr\)_minmax\(16rem\,0\.7fr\)\]{grid-template-columns:minmax(0,1.6fr) minmax(16rem,.7fr)}.grid-cols-\[minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr)}.grid-cols-\[minmax\(0\,1fr\)_13rem\]{grid-template-columns:minmax(0,1fr) 13rem}.grid-cols-\[minmax\(0\,1fr\)_14rem\]{grid-template-columns:minmax(0,1fr) 14rem}.grid-cols-\[minmax\(0\,1fr\)_2\.5rem\]{grid-template-columns:minmax(0,1fr) 2.5rem}.grid-cols-\[minmax\(0\,1fr\)_20rem\]{grid-template-columns:minmax(0,1fr) 20rem}.grid-cols-\[minmax\(0\,1fr\)_4\.75rem\]{grid-template-columns:minmax(0,1fr) 4.75rem}.grid-cols-\[minmax\(0\,1fr\)_8rem\]{grid-template-columns:minmax(0,1fr) 8rem}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,0\.7fr\)_minmax\(0\,1\.9fr\)_minmax\(0\,0\.7fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,.7fr) minmax(0,1.9fr) minmax(0,.7fr)}.grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.grid-cols-\[minmax\(17rem\,1fr\)_minmax\(0\,133\.333vh\)_minmax\(17rem\,1fr\)\]{grid-template-columns:minmax(17rem,1fr) minmax(0,133.333vh) minmax(17rem,1fr)}.grid-cols-\[repeat\(auto-fit\,minmax\(min\(22\.5rem\,100\%\)\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(min(22.5rem,100%),1fr))}.grid-cols-\[repeat\(auto-fit\,minmax\(min\(9rem\,100\%\)\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(min(9rem,100%),1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr}.grid-rows-\[auto_auto_1fr\]{grid-template-rows:auto auto 1fr}.grid-rows-\[auto_auto_1fr_auto\]{grid-template-rows:auto auto 1fr auto}.grid-rows-\[auto_auto_auto_1fr\]{grid-template-rows:auto auto auto 1fr}.grid-rows-\[auto_minmax\(0\,1fr\)\]{grid-template-rows:auto minmax(0,1fr)}.grid-rows-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-rows:auto minmax(0,1fr) auto}.grid-rows-\[minmax\(0\,1fr\)\]{grid-template-rows:minmax(0,1fr)}.grid-rows-\[minmax\(0\,1fr\)_auto\]{grid-template-rows:minmax(0,1fr) auto}.grid-rows-\[minmax\(0\,1fr\)_minmax\(0\,1fr\)_minmax\(0\,1fr\)\]{grid-template-rows:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr)}.grid-rows-\[minmax\(0\,1fr\)_minmax\(7rem\,0\.22fr\)\]{grid-template-rows:minmax(0,1fr) minmax(7rem,.22fr)}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-stretch{justify-content:stretch}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-\[0\.4vh\]{gap:.4vh}.gap-\[0\.55vh\]{gap:.55vh}.gap-\[0\.8vw\]{gap:.8vw}.gap-\[1vh\]{gap:1vh}.gap-\[1vw\]{gap:1vw}.gap-\[3vh\]{gap:3vh}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-0\.5{row-gap:.125rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-bl-\[4\.25rem\]{border-bottom-left-radius:4.25rem}.rounded-bl-xl{border-bottom-left-radius:.75rem}.rounded-br-\[12\.75rem\]{border-bottom-right-radius:12.75rem}.rounded-br-\[4\.25rem\]{border-bottom-right-radius:4.25rem}.rounded-br-xl{border-bottom-right-radius:.75rem}.rounded-tl-\[4\.25rem\]{border-top-left-radius:4.25rem}.rounded-tl-lg{border-top-left-radius:.5rem}.rounded-tr-\[4\.25rem\]{border-top-right-radius:4.25rem}.rounded-tr-xl{border-top-right-radius:.75rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-4{border-width:4px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity))}.border-amber-200\/30{border-color:#fde68a4d}.border-amber-200\/70{border-color:#fde68ab3}.border-amber-200\/90{border-color:#fde68ae6}.border-amber-300\/60{border-color:#fcd34d99}.border-amber-300\/70{border-color:#fcd34db3}.border-amber-300\/80{border-color:#fcd34dcc}.border-amber-400{--tw-border-opacity: 1;border-color:rgb(251 191 36 / var(--tw-border-opacity))}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-400\/60{border-color:#fbbf2499}.border-amber-500\/50{border-color:#f59e0b80}.border-amber-600\/60{border-color:#d9770699}.border-amber-700\/60{border-color:#b4530999}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.border-cyan-200{--tw-border-opacity: 1;border-color:rgb(165 243 252 / var(--tw-border-opacity))}.border-cyan-200\/90{border-color:#a5f3fce6}.border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.border-cyan-300\/60{border-color:#67e8f999}.border-cyan-300\/70{border-color:#67e8f9b3}.border-cyan-300\/80{border-color:#67e8f9cc}.border-cyan-500\/40{border-color:#06b6d466}.border-emerald-100\/80{border-color:#d1fae5cc}.border-emerald-200{--tw-border-opacity: 1;border-color:rgb(167 243 208 / var(--tw-border-opacity))}.border-emerald-200\/70{border-color:#a7f3d0b3}.border-emerald-200\/90{border-color:#a7f3d0e6}.border-emerald-300{--tw-border-opacity: 1;border-color:rgb(110 231 183 / var(--tw-border-opacity))}.border-emerald-300\/70{border-color:#6ee7b7b3}.border-emerald-400\/60{border-color:#34d39999}.border-emerald-500\/40{border-color:#10b98166}.border-emerald-700\/60{border-color:#04785799}.border-emerald-700\/70{border-color:#047857b3}.border-emerald-950{--tw-border-opacity: 1;border-color:rgb(2 44 34 / var(--tw-border-opacity))}.border-fuchsia-300\/70{border-color:#f0abfcb3}.border-green-400{--tw-border-opacity: 1;border-color:rgb(74 222 128 / var(--tw-border-opacity))}.border-indigo-200\/70{border-color:#c7d2feb3}.border-indigo-200\/95{border-color:#c7d2fef2}.border-indigo-300\/70{border-color:#a5b4fcb3}.border-indigo-400\/30{border-color:#818cf84d}.border-indigo-700{--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}.border-indigo-700\/60{border-color:#4338ca99}.border-neutral-500{--tw-border-opacity: 1;border-color:rgb(115 115 115 / var(--tw-border-opacity))}.border-neutral-500\/50{border-color:#73737380}.border-neutral-500\/60{border-color:#73737399}.border-neutral-500\/70{border-color:#737373b3}.border-neutral-600{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity))}.border-neutral-700{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity))}.border-neutral-700\/70{border-color:#404040b3}.border-pink-200{--tw-border-opacity: 1;border-color:rgb(251 207 232 / var(--tw-border-opacity))}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity))}.border-red-300\/80{border-color:#fca5a5cc}.border-red-300\/90{border-color:#fca5a5e6}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity))}.border-red-400\/60{border-color:#f8717199}.border-rose-300{--tw-border-opacity: 1;border-color:rgb(253 164 175 / var(--tw-border-opacity))}.border-sky-200{--tw-border-opacity: 1;border-color:rgb(186 230 253 / var(--tw-border-opacity))}.border-sky-300{--tw-border-opacity: 1;border-color:rgb(125 211 252 / var(--tw-border-opacity))}.border-sky-400\/50{border-color:#38bdf880}.border-sky-400\/70{border-color:#38bdf8b3}.border-slate-200\/30{border-color:#e2e8f04d}.border-slate-400{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.border-slate-400\/60{border-color:#94a3b899}.border-slate-500\/70{border-color:#64748bb3}.border-slate-600{--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}.border-slate-600\/40{border-color:#47556966}.border-slate-600\/60{border-color:#47556999}.border-slate-600\/70{border-color:#475569b3}.border-slate-600\/80{border-color:#475569cc}.border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.border-slate-700\/60{border-color:#33415599}.border-slate-700\/70{border-color:#334155b3}.border-slate-700\/80{border-color:#334155cc}.border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.border-slate-800\/60{border-color:#1e293b99}.border-slate-800\/80{border-color:#1e293bcc}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.border-white\/10{border-color:#ffffff1a}.border-white\/40{border-color:#fff6}.border-white\/80{border-color:#fffc}.border-l-neutral-600{--tw-border-opacity: 1;border-left-color:rgb(82 82 82 / var(--tw-border-opacity))}.\!bg-black{--tw-bg-opacity: 1 !important;background-color:rgb(0 0 0 / var(--tw-bg-opacity))!important}.bg-amber-100\/95{background-color:#fef3c7f2}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity))}.bg-amber-400\/80{background-color:#fbbf24cc}.bg-amber-500{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity))}.bg-amber-500\/30{background-color:#f59e0b4d}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity))}.bg-amber-600\/70{background-color:#d97706b3}.bg-amber-600\/80{background-color:#d97706cc}.bg-amber-700\/20{background-color:#b4530933}.bg-amber-700\/30{background-color:#b453094d}.bg-amber-700\/35{background-color:#b4530959}.bg-amber-700\/50{background-color:#b4530980}.bg-amber-700\/80{background-color:#b45309cc}.bg-amber-800{--tw-bg-opacity: 1;background-color:rgb(146 64 14 / var(--tw-bg-opacity))}.bg-amber-900{--tw-bg-opacity: 1;background-color:rgb(120 53 15 / var(--tw-bg-opacity))}.bg-amber-900\/40{background-color:#78350f66}.bg-amber-900\/60{background-color:#78350f99}.bg-amber-900\/90{background-color:#78350fe6}.bg-amber-950{--tw-bg-opacity: 1;background-color:rgb(69 26 3 / var(--tw-bg-opacity))}.bg-amber-950\/35{background-color:#451a0359}.bg-amber-950\/40{background-color:#451a0366}.bg-amber-950\/70{background-color:#451a03b3}.bg-amber-950\/75{background-color:#451a03bf}.bg-amber-950\/90{background-color:#451a03e6}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-black\/45{background-color:#00000073}.bg-black\/50{background-color:#00000080}.bg-black\/55{background-color:#0000008c}.bg-black\/60{background-color:#0009}.bg-black\/70{background-color:#000000b3}.bg-black\/75{background-color:#000000bf}.bg-black\/80{background-color:#000c}.bg-black\/90{background-color:#000000e6}.bg-black\/95{background-color:#000000f2}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-cyan-300{--tw-bg-opacity: 1;background-color:rgb(103 232 249 / var(--tw-bg-opacity))}.bg-cyan-300\/20{background-color:#67e8f933}.bg-cyan-500{--tw-bg-opacity: 1;background-color:rgb(6 182 212 / var(--tw-bg-opacity))}.bg-cyan-500\/80{background-color:#06b6d4cc}.bg-cyan-900{--tw-bg-opacity: 1;background-color:rgb(22 78 99 / var(--tw-bg-opacity))}.bg-cyan-900\/45{background-color:#164e6373}.bg-cyan-900\/90{background-color:#164e63e6}.bg-cyan-950\/60{background-color:#08334499}.bg-emerald-200{--tw-bg-opacity: 1;background-color:rgb(167 243 208 / var(--tw-bg-opacity))}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}.bg-emerald-500\/45{background-color:#10b98173}.bg-emerald-500\/80{background-color:#10b981cc}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity))}.bg-emerald-600\/70{background-color:#059669b3}.bg-emerald-600\/80{background-color:#059669cc}.bg-emerald-700{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity))}.bg-emerald-700\/20{background-color:#04785733}.bg-emerald-700\/60{background-color:#04785799}.bg-emerald-700\/70{background-color:#047857b3}.bg-emerald-800{--tw-bg-opacity: 1;background-color:rgb(6 95 70 / var(--tw-bg-opacity))}.bg-emerald-900{--tw-bg-opacity: 1;background-color:rgb(6 78 59 / var(--tw-bg-opacity))}.bg-emerald-900\/60{background-color:#064e3b99}.bg-emerald-900\/90{background-color:#064e3be6}.bg-emerald-950{--tw-bg-opacity: 1;background-color:rgb(2 44 34 / var(--tw-bg-opacity))}.bg-emerald-950\/50{background-color:#022c2280}.bg-emerald-950\/90{background-color:#022c22e6}.bg-fuchsia-200{--tw-bg-opacity: 1;background-color:rgb(245 208 254 / var(--tw-bg-opacity))}.bg-fuchsia-600{--tw-bg-opacity: 1;background-color:rgb(192 38 211 / var(--tw-bg-opacity))}.bg-fuchsia-700{--tw-bg-opacity: 1;background-color:rgb(162 28 175 / var(--tw-bg-opacity))}.bg-fuchsia-800{--tw-bg-opacity: 1;background-color:rgb(134 25 143 / var(--tw-bg-opacity))}.bg-fuchsia-900\/45{background-color:#701a7573}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.bg-indigo-600\/70{background-color:#4f46e5b3}.bg-indigo-900{--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}.bg-indigo-900\/60{background-color:#312e8199}.bg-indigo-950{--tw-bg-opacity: 1;background-color:rgb(30 27 75 / var(--tw-bg-opacity))}.bg-indigo-950\/60{background-color:#1e1b4b99}.bg-indigo-950\/90{background-color:#1e1b4be6}.bg-neutral-600{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity))}.bg-neutral-600\/80{background-color:#525252cc}.bg-neutral-700{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity))}.bg-neutral-700\/90{background-color:#404040e6}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity))}.bg-neutral-800\/80{background-color:#262626cc}.bg-neutral-900{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity))}.bg-neutral-900\/100{background-color:#171717}.bg-neutral-900\/60{background-color:#17171799}.bg-neutral-900\/70{background-color:#171717b3}.bg-neutral-900\/95{background-color:#171717f2}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity))}.bg-neutral-950\/90{background-color:#0a0a0ae6}.bg-pink-500{--tw-bg-opacity: 1;background-color:rgb(236 72 153 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-red-500\/45{background-color:#ef444473}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.bg-red-600\/70{background-color:#dc2626b3}.bg-red-700{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity))}.bg-red-700\/20{background-color:#b91c1c33}.bg-red-700\/60{background-color:#b91c1c99}.bg-red-700\/80{background-color:#b91c1ccc}.bg-red-800{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity))}.bg-red-900\/35{background-color:#7f1d1d59}.bg-red-900\/40{background-color:#7f1d1d66}.bg-red-900\/45{background-color:#7f1d1d73}.bg-red-900\/80{background-color:#7f1d1dcc}.bg-red-950{--tw-bg-opacity: 1;background-color:rgb(69 10 10 / var(--tw-bg-opacity))}.bg-red-950\/70{background-color:#450a0ab3}.bg-red-950\/90{background-color:#450a0ae6}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity))}.bg-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity))}.bg-sky-500{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.bg-sky-600{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity))}.bg-sky-700{--tw-bg-opacity: 1;background-color:rgb(3 105 161 / var(--tw-bg-opacity))}.bg-sky-700\/20{background-color:#0369a133}.bg-sky-700\/70{background-color:#0369a1b3}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity))}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}.bg-slate-600\/70{background-color:#475569b3}.bg-slate-700{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.bg-slate-700\/30{background-color:#3341554d}.bg-slate-700\/60{background-color:#33415599}.bg-slate-700\/70{background-color:#334155b3}.bg-slate-700\/80{background-color:#334155cc}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-slate-800\/60{background-color:#1e293b99}.bg-slate-800\/70{background-color:#1e293bb3}.bg-slate-800\/80{background-color:#1e293bcc}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.bg-slate-900\/35{background-color:#0f172a59}.bg-slate-900\/40{background-color:#0f172a66}.bg-slate-900\/70{background-color:#0f172ab3}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity))}.bg-slate-950\/35{background-color:#02061759}.bg-slate-950\/80{background-color:#020617cc}.bg-slate-950\/85{background-color:#020617d9}.bg-slate-950\/90{background-color:#020617e6}.bg-slate-950\/95{background-color:#020617f2}.bg-transparent{background-color:transparent}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-white\/10{background-color:#ffffff1a}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.bg-zinc-900{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity))}.bg-zinc-900\/90{background-color:#18181be6}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-lime-800{--tw-gradient-from: #3f6212 var(--tw-gradient-from-position);--tw-gradient-to: rgb(63 98 18 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-neutral-800{--tw-gradient-from: #262626 var(--tw-gradient-from-position);--tw-gradient-to: rgb(38 38 38 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-neutral-700{--tw-gradient-to: rgb(64 64 64 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #404040 var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-neutral-600{--tw-gradient-to: #525252 var(--tw-gradient-to-position)}.to-teal-800{--tw-gradient-to: #115e59 var(--tw-gradient-to-position)}.bg-cover{background-size:cover}.bg-center{background-position:center}.bg-no-repeat{background-repeat:no-repeat}.fill-slate-100{fill:#f1f5f9}.fill-slate-200{fill:#e2e8f0}.fill-slate-400{fill:#94a3b8}.fill-white{fill:#fff}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[0\.85vw\]{padding:.85vw}.px-0{padding-left:0;padding-right:0}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.px-\[1\.2vw\]{padding-left:1.2vw;padding-right:1.2vw}.px-\[1\.6vw\]{padding-left:1.6vw;padding-right:1.6vw}.px-\[1vw\]{padding-left:1vw;padding-right:1vw}.px-\[3vw\]{padding-left:3vw;padding-right:3vw}.px-\[4vw\]{padding-left:4vw;padding-right:4vw}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-7{padding-top:1.75rem;padding-bottom:1.75rem}.py-\[0\.55vh\]{padding-top:.55vh;padding-bottom:.55vh}.py-\[1px\]{padding-top:1px;padding-bottom:1px}.py-\[2\.5vh\]{padding-top:2.5vh;padding-bottom:2.5vh}.py-\[2px\]{padding-top:2px;padding-bottom:2px}.py-\[4vh\]{padding-top:4vh;padding-bottom:4vh}.pb-0{padding-bottom:0}.pb-0\.5{padding-bottom:.125rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pr-0{padding-right:0}.pr-1{padding-right:.25rem}.pt-0\.5{padding-top:.125rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.text-\[0\.45rem\]{font-size:.45rem}.text-\[0\.48rem\]{font-size:.48rem}.text-\[0\.52rem\]{font-size:.52rem}.text-\[0\.55em\]{font-size:.55em}.text-\[0\.55rem\]{font-size:.55rem}.text-\[0\.58rem\]{font-size:.58rem}.text-\[0\.5rem\]{font-size:.5rem}.text-\[0\.62rem\]{font-size:.62rem}.text-\[0\.65em\]{font-size:.65em}.text-\[0\.65rem\]{font-size:.65rem}.text-\[0\.68rem\]{font-size:.68rem}.text-\[0\.6rem\]{font-size:.6rem}.text-\[0\.72rem\]{font-size:.72rem}.text-\[0\.75rem\]{font-size:.75rem}.text-\[0\.78rem\]{font-size:.78rem}.text-\[0\.7rem\]{font-size:.7rem}.text-\[0\.82rem\]{font-size:.82rem}.text-\[0\.85rem\]{font-size:.85rem}.text-\[0\.8rem\]{font-size:.8rem}.text-\[1\.1rem\]{font-size:1.1rem}.text-\[1\.5rem\]{font-size:1.5rem}.text-\[1\.75rem\]{font-size:1.75rem}.text-\[17vw\]{font-size:17vw}.text-\[1rem\]{font-size:1rem}.text-\[2\.5vw\]{font-size:2.5vw}.text-\[2\.6vw\]{font-size:2.6vw}.text-\[3\.1vw\]{font-size:3.1vw}.text-\[3\.375rem\]{font-size:3.375rem}.text-\[3vw\]{font-size:3vw}.text-\[4vw\]{font-size:4vw}.text-\[6vw\]{font-size:6vw}.text-\[7vw\]{font-size:7vw}.text-\[clamp\(1\.25rem\,2\.5vh\,2\.6rem\)\]{font-size:clamp(1.25rem,2.5vh,2.6rem)}.text-\[clamp\(1\.2rem\,2\.9vh\,3rem\)\]{font-size:clamp(1.2rem,2.9vh,3rem)}.text-\[clamp\(2\.1rem\,5\.1vh\,5\.6rem\)\]{font-size:clamp(2.1rem,5.1vh,5.6rem)}.text-\[clamp\(2\.5rem\,5\.3vh\,5\.8rem\)\]{font-size:clamp(2.5rem,5.3vh,5.8rem)}.text-\[clamp\(2\.5rem\,7vh\,7rem\)\]{font-size:clamp(2.5rem,7vh,7rem)}.text-\[clamp\(2\.5rem\,8vh\,8rem\)\]{font-size:clamp(2.5rem,8vh,8rem)}.text-\[clamp\(2rem\,5vh\,5\.2rem\)\]{font-size:clamp(2rem,5vh,5.2rem)}.text-\[clamp\(3\.2rem\,11vh\,11rem\)\]{font-size:clamp(3.2rem,11vh,11rem)}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-\[0\.95\]{line-height:.95}.leading-none{line-height:1}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-normal{letter-spacing:0em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity))}.text-amber-200\/80{color:#fde68acc}.text-amber-200\/85{color:#fde68ad9}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity))}.text-amber-50{--tw-text-opacity: 1;color:rgb(255 251 235 / var(--tw-text-opacity))}.text-amber-50\/90{color:#fffbebe6}.text-amber-950{--tw-text-opacity: 1;color:rgb(69 26 3 / var(--tw-text-opacity))}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.text-cyan-50{--tw-text-opacity: 1;color:rgb(236 254 255 / var(--tw-text-opacity))}.text-cyan-950{--tw-text-opacity: 1;color:rgb(8 51 68 / var(--tw-text-opacity))}.text-emerald-100{--tw-text-opacity: 1;color:rgb(209 250 229 / var(--tw-text-opacity))}.text-emerald-100\/80{color:#d1fae5cc}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity))}.text-emerald-50{--tw-text-opacity: 1;color:rgb(236 253 245 / var(--tw-text-opacity))}.text-emerald-50\/90{color:#ecfdf5e6}.text-emerald-950{--tw-text-opacity: 1;color:rgb(2 44 34 / var(--tw-text-opacity))}.text-fuchsia-50{--tw-text-opacity: 1;color:rgb(253 244 255 / var(--tw-text-opacity))}.text-fuchsia-900{--tw-text-opacity: 1;color:rgb(112 26 117 / var(--tw-text-opacity))}.text-indigo-200{--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}.text-indigo-50{--tw-text-opacity: 1;color:rgb(238 242 255 / var(--tw-text-opacity))}.text-indigo-50\/90{color:#eef2ffe6}.text-lime-300{--tw-text-opacity: 1;color:rgb(190 242 100 / var(--tw-text-opacity))}.text-lime-400{--tw-text-opacity: 1;color:rgb(163 230 53 / var(--tw-text-opacity))}.text-neutral-100{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity))}.text-neutral-200{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity))}.text-neutral-300{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity))}.text-neutral-400{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity))}.text-neutral-50{--tw-text-opacity: 1;color:rgb(250 250 250 / var(--tw-text-opacity))}.text-neutral-500{--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity))}.text-red-100{--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity))}.text-red-100\/90{color:#fee2e2e6}.text-red-100\/95{color:#fee2e2f2}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-50{--tw-text-opacity: 1;color:rgb(254 242 242 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity))}.text-sky-100{--tw-text-opacity: 1;color:rgb(224 242 254 / var(--tw-text-opacity))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity))}.text-sky-300{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity))}.text-sky-50{--tw-text-opacity: 1;color:rgb(240 249 255 / var(--tw-text-opacity))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-400\/60{color:#94a3b899}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-slate-950{--tw-text-opacity: 1;color:rgb(2 6 23 / var(--tw-text-opacity))}.text-teal-400{--tw-text-opacity: 1;color:rgb(45 212 191 / var(--tw-text-opacity))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-white\/75{color:#ffffffbf}.text-white\/90{color:#ffffffe6}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity))}.accent-amber-500{accent-color:#f59e0b}.accent-cyan-500{accent-color:#06b6d4}.accent-emerald-400{accent-color:#34d399}.accent-emerald-500{accent-color:#10b981}.accent-red-500{accent-color:#ef4444}.accent-sky-500{accent-color:#0ea5e9}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_1px_0_rgba\(255\,255\,255\,0\.05\)_inset\,0_10px_24px_rgba\(0\,0\,0\,0\.28\)\]{--tw-shadow: 0 1px 0 rgba(255,255,255,.05) inset,0 10px 24px rgba(0,0,0,.28);--tw-shadow-colored: inset 0 1px 0 var(--tw-shadow-color), 0 10px 24px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color: rgb(0 0 0 / .4);--tw-shadow: var(--tw-shadow-colored)}.shadow-cyan-950\/50{--tw-shadow-color: rgb(8 51 68 / .5);--tw-shadow: var(--tw-shadow-colored)}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-8{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-amber-200\/60{--tw-ring-color: rgb(253 230 138 / .6)}.ring-amber-300{--tw-ring-opacity: 1;--tw-ring-color: rgb(252 211 77 / var(--tw-ring-opacity))}.ring-amber-300\/80{--tw-ring-color: rgb(252 211 77 / .8)}.ring-cyan-200\/90{--tw-ring-color: rgb(165 243 252 / .9)}.ring-emerald-300\/50{--tw-ring-color: rgb(110 231 183 / .5)}.ring-emerald-300\/70{--tw-ring-color: rgb(110 231 183 / .7)}.ring-emerald-300\/80{--tw-ring-color: rgb(110 231 183 / .8)}.ring-indigo-300\/70{--tw-ring-color: rgb(165 180 252 / .7)}.ring-red-300\/60{--tw-ring-color: rgb(252 165 165 / .6)}.ring-red-300\/80{--tw-ring-color: rgb(252 165 165 / .8)}.ring-red-500\/70{--tw-ring-color: rgb(239 68 68 / .7)}.ring-red-500\/80{--tw-ring-color: rgb(239 68 68 / .8)}.ring-red-500\/90{--tw-ring-color: rgb(239 68 68 / .9)}.ring-sky-300\/50{--tw-ring-color: rgb(125 211 252 / .5)}.ring-sky-400\/80{--tw-ring-color: rgb(56 189 248 / .8)}.ring-slate-400\/40{--tw-ring-color: rgb(148 163 184 / .4)}.ring-slate-400\/70{--tw-ring-color: rgb(148 163 184 / .7)}.ring-slate-600{--tw-ring-opacity: 1;--tw-ring-color: rgb(71 85 105 / var(--tw-ring-opacity))}.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)}.grayscale{--tw-grayscale: grayscale(100%);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)}.invert{--tw-invert: invert(100%);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)}.sepia{--tw-sepia: sepia(100%);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(4px);-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{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\,border-radius\,background-color\]{transition-property:width,height,border-radius,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\]{transition-property:width,height;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,opacity\]{transition-property:width,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-700{transition-duration:.7s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\@container{container-type:inline-size}.\[clip-path\:polygon\(0_0\,0_100\%\,100\%_100\%\)\]{clip-path:polygon(0 0,0 100%,100% 100%)}.\[clip-path\:polygon\(0_0\,100\%_0\,0_100\%\)\]{clip-path:polygon(0 0,100% 0,0 100%)}.\[clip-path\:polygon\(0_0\,100\%_0\,100\%_100\%\)\]{clip-path:polygon(0 0,100% 0,100% 100%)}.\[clip-path\:polygon\(100\%_0\,0_100\%\,100\%_100\%\)\]{clip-path:polygon(100% 0,0 100%,100% 100%)}.\[writing-mode\:vertical-rl\]{writing-mode:vertical-rl}:root{font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}body{margin:0;min-height:100vh;--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}html,body,*{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar,*::-webkit-scrollbar{display:none;width:0;height:0}.placeholder\:text-slate-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.last\:mb-0:last-child{margin-bottom:0}.last\:border-0:last-child{border-width:0px}.hover\:-translate-y-0\.5:hover{--tw-translate-y: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-110:hover{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-green-100:hover{--tw-border-opacity: 1;border-color:rgb(220 252 231 / var(--tw-border-opacity))}.hover\:border-rose-500:hover{--tw-border-opacity: 1;border-color:rgb(244 63 94 / var(--tw-border-opacity))}.hover\:border-sky-500:hover{--tw-border-opacity: 1;border-color:rgb(14 165 233 / var(--tw-border-opacity))}.hover\:border-white:hover{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.hover\:border-white\/60:hover{border-color:#fff9}.hover\:bg-amber-400:hover{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity))}.hover\:bg-amber-600:hover{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity))}.hover\:bg-amber-800:hover{--tw-bg-opacity: 1;background-color:rgb(146 64 14 / var(--tw-bg-opacity))}.hover\:bg-amber-900:hover{--tw-bg-opacity: 1;background-color:rgb(120 53 15 / var(--tw-bg-opacity))}.hover\:bg-amber-900\/95:hover{background-color:#78350ff2}.hover\:bg-black:hover{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.hover\:bg-black\/80:hover{background-color:#000c}.hover\:bg-cyan-400:hover{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity))}.hover\:bg-cyan-800:hover{--tw-bg-opacity: 1;background-color:rgb(21 94 117 / var(--tw-bg-opacity))}.hover\:bg-emerald-400:hover{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity))}.hover\:bg-emerald-500:hover{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}.hover\:bg-emerald-700:hover{--tw-bg-opacity: 1;background-color:rgb(4 120 87 / var(--tw-bg-opacity))}.hover\:bg-emerald-900\/95:hover{background-color:#064e3bf2}.hover\:bg-fuchsia-500:hover{--tw-bg-opacity: 1;background-color:rgb(217 70 239 / var(--tw-bg-opacity))}.hover\:bg-green-400:hover{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity))}.hover\:bg-indigo-500:hover{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.hover\:bg-indigo-800:hover{--tw-bg-opacity: 1;background-color:rgb(55 48 163 / var(--tw-bg-opacity))}.hover\:bg-indigo-900:hover{--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}.hover\:bg-neutral-700:hover{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity))}.hover\:bg-pink-400:hover{--tw-bg-opacity: 1;background-color:rgb(244 114 182 / var(--tw-bg-opacity))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.hover\:bg-red-900:hover{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity))}.hover\:bg-rose-500:hover{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity))}.hover\:bg-sky-500:hover{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity))}.hover\:bg-slate-600:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}.hover\:bg-slate-700:hover{--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.hover\:bg-slate-700\/70:hover{background-color:#334155b3}.hover\:bg-slate-900:hover{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-zinc-900:hover{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-xl:hover{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);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)}.focus\:bg-white\/10:focus{background-color:#ffffff1a}.focus\:text-white:focus{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-emerald-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(16 185 129 / var(--tw-ring-opacity))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline:focus-visible{outline-style:solid}.focus-visible\:outline-1:focus-visible{outline-width:1px}.focus-visible\:outline-offset-1:focus-visible{outline-offset:1px}.focus-visible\:outline-slate-500:focus-visible{outline-color:#64748b}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-4:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-amber-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity))}.focus-visible\:ring-amber-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(252 211 77 / var(--tw-ring-opacity))}.focus-visible\:ring-cyan-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(165 243 252 / var(--tw-ring-opacity))}.focus-visible\:ring-cyan-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(103 232 249 / var(--tw-ring-opacity))}.focus-visible\:ring-emerald-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(167 243 208 / var(--tw-ring-opacity))}.focus-visible\:ring-emerald-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(110 231 183 / var(--tw-ring-opacity))}.focus-visible\:ring-indigo-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity))}.focus-visible\:ring-indigo-300:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(165 180 252 / var(--tw-ring-opacity))}.focus-visible\:ring-red-200:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity))}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.99\]:active{--tw-scale-x: .99;--tw-scale-y: .99;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:brightness-125:active{--tw-brightness: brightness(1.25);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)}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:cursor-wait:disabled{cursor:wait}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-45:disabled{opacity:.45}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}.disabled\:opacity-70:disabled{opacity:.7}.disabled\:opacity-75:disabled{opacity:.75}@container (min-width: 20rem){.\@xs\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@container (min-width: 24rem){.\@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\:grid-cols-\[minmax\(0\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr)}.\@sm\:grid-cols-\[minmax\(0\,1\.4fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr)}.\@sm\:flex-row{flex-direction:row}.\@sm\:flex-col{flex-direction:column}.\@sm\:gap-0{gap:0px}.\@sm\:overflow-hidden{overflow:hidden}.\@sm\:whitespace-nowrap{white-space:nowrap}.\@sm\:border-b-0{border-bottom-width:0px}.\@sm\:border-r{border-right-width:1px}.\@sm\:py-0{padding-top:0;padding-bottom:0}.\@sm\:text-\[0\.55em\]{font-size:.55em}}@container (min-width: 28rem){.\@md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\@\[28rem\]\:col-span-2{grid-column:span 2 / span 2}.\@\[28rem\]\:grid{display:grid}.\@\[28rem\]\:h-32{height:8rem}.\@\[28rem\]\:min-w-0{min-width:0px}.\@\[28rem\]\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\@\[28rem\]\:grid-cols-\[minmax\(0\,1\.15fr\)_minmax\(0\,0\.85fr\)_minmax\(0\,0\.85fr\)\]{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr) minmax(0,.85fr)}.\@\[28rem\]\:grid-cols-\[minmax\(0\,1fr\)_9rem_10rem\]{grid-template-columns:minmax(0,1fr) 9rem 10rem}.\@\[28rem\]\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.\@\[28rem\]\:text-right{text-align:right}}@container (min-width: 32rem){.\@lg\:w-auto{width:auto}.\@lg\:min-w-24{min-width:6rem}.\@lg\:flex-row{flex-direction:row}.\@lg\:items-center{align-items:center}.\@lg\:justify-between{justify-content:space-between}.\@\[32rem\]\:col-span-2{grid-column:span 2 / span 2}.\@\[32rem\]\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\@\[32rem\]\:grid-cols-\[minmax\(0\,1\.25fr\)_minmax\(0\,0\.9fr\)\]{grid-template-columns:minmax(0,1.25fr) minmax(0,.9fr)}}@media(prefers-reduced-motion:reduce){.motion-reduce\:transition-none{transition-property:none}}@media(max-width:520px){.max-\[520px\]\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-\[520px\]\:justify-start{justify-content:flex-start}}@media(max-width:420px){.max-\[420px\]\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-\[420px\]\:justify-start{justify-content:flex-start}}@media(min-width:640px){.sm\:block{display:block}.sm\:min-h-\[18rem\]{min-height:18rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media(min-width:768px){.md\:h-full{height:100%}.md\:h-screen{height:100vh}.md\:min-h-0{min-height:0px}.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-\[0\.9fr_1fr_1\.3fr\]{grid-template-columns:.9fr 1fr 1.3fr}.md\:grid-cols-\[minmax\(0\,1\.5fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr)}.md\:grid-cols-\[minmax\(0\,1fr\)_10rem\]{grid-template-columns:minmax(0,1fr) 10rem}.md\:grid-cols-\[minmax\(0\,1fr\)_18rem\]{grid-template-columns:minmax(0,1fr) 18rem}.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:grid-cols-\[minmax\(0\,1fr\)_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto}.md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,0\.7fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,.7fr)}.md\:grid-cols-\[minmax\(16rem\,0\.8fr\)_minmax\(0\,1\.2fr\)\]{grid-template-columns:minmax(16rem,.8fr) minmax(0,1.2fr)}.md\:items-start{align-items:flex-start}.md\:overflow-hidden{overflow:hidden}.md\:overflow-y-auto{overflow-y:auto}.md\:text-lg{font-size:1.125rem;line-height:1.75rem}.md\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media(min-width:1024px){.lg\:grid-cols-\[24rem_minmax\(0\,1fr\)\]{grid-template-columns:24rem minmax(0,1fr)}.lg\:grid-cols-\[minmax\(0\,1fr\)_20rem\]{grid-template-columns:minmax(0,1fr) 20rem}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}}@media(min-width:1280px){.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}}.\[\&_img\]\:object-contain img{-o-object-fit:contain;object-fit:contain}.\[\&_video\]\:object-contain video{-o-object-fit:contain;object-fit:contain}.page-theme{background-color:#050505;background-attachment:fixed}.page-theme-preview{background-attachment:scroll;background-position:center}.page-theme-black{background-color:#000;background-image:none}.page-theme-green-mode{background-color:#0f0;background-image:none}.page-theme-pride-mix{background-image:repeating-linear-gradient(45deg,#5bcefa 0 48px,#f5a9b8 48px 96px,#fff 96px 144px,#f5a9b8 144px 192px,#5bcefa 192px 240px,#e40303 240px 288px,#ff8c00 288px 336px,#ffed00 336px 384px,#00a651 384px 432px,#06f 432px 480px,#9b2fae 480px 528px)}.page-theme-rainbow{background-image:repeating-linear-gradient(45deg,#e40303 0 48px,#ff8c00 48px 96px,#ffed00 96px 144px,#008026 144px 192px,#004dff 192px 240px,#750787 240px 288px)}.page-theme-progress-pride{background-image:repeating-linear-gradient(45deg,#000 0 48px,#784f17 48px 96px,#5bcefa 96px 144px,#f5a9b8 144px 192px,#fff 192px 240px,#e40303 240px 288px,#ff8c00 288px 336px,#ffed00 336px 384px,#008026 384px 432px,#004dff 432px 480px,#750787 480px 528px)}.page-theme-transgender{background-image:repeating-linear-gradient(45deg,#5bcefa 0 48px,#f5a9b8 48px 96px,#fff 96px 144px,#f5a9b8 144px 192px,#5bcefa 192px 240px)}.page-theme-bisexual{background-image:repeating-linear-gradient(45deg,#d60270 0 48px,#9b4f96 48px 96px,#0038a8 96px 144px)}.page-theme-lesbian{background-image:repeating-linear-gradient(45deg,#d52d00 0 48px,#ef7627 48px 96px,#ff9a56 96px 144px,#fff 144px 192px,#d162a4 192px 240px,#b55690 240px 288px,#a30262 288px 336px)}.page-theme-nonbinary{background-image:repeating-linear-gradient(45deg,#fff430 0 48px,#fff 48px 96px,#9c59d1 96px 144px,#2d2d2d 144px 192px)}.page-theme-pansexual{background-image:repeating-linear-gradient(45deg,#ff218c 0 48px,#ffd800 48px 96px,#21b1ff 96px 144px)}.page-theme-asexual{background-image:repeating-linear-gradient(45deg,#111 0 48px,#a3a3a3 48px 96px,#fff 96px 144px,purple 144px 192px)}.page-theme-aurora{background-color:#03120f;background-image:radial-gradient(circle at 18% 24%,rgba(45,212,191,.95),transparent 34%),radial-gradient(circle at 76% 18%,rgba(168,85,247,.9),transparent 38%),radial-gradient(circle at 56% 82%,rgba(34,197,94,.88),transparent 42%),linear-gradient(125deg,#020617,#0f172a 45%,#082f49);background-size:34rem 28rem,38rem 32rem,42rem 36rem,auto}.page-theme-synthwave-grid{background-color:#090018;background-image:linear-gradient(rgba(34,211,238,.7) 2px,transparent 2px),linear-gradient(90deg,rgba(236,72,153,.72) 2px,transparent 2px),radial-gradient(ellipse at 50% 100%,#7e22ce 0,#1e1b4b 38%,#090018 72%);background-size:54px 54px,54px 54px,auto}.page-theme-neon-checker{background-color:#111827;background-image:conic-gradient(from 45deg,#06b6d4 0 25%,#7c3aed 0 50%,#ec4899 0 75%,#111827 0);background-size:84px 84px}.page-theme-ocean-current{background-color:#082f49;background-image:repeating-linear-gradient(35deg,rgba(103,232,249,.72) 0 12px,transparent 12px 54px),repeating-linear-gradient(-35deg,rgba(14,116,144,.72) 0 21px,transparent 21px 72px),linear-gradient(90deg,#0c4a6e,#0891b2,#164e63)}.page-theme-ember-lattice{background-color:#1c0704;background-image:repeating-linear-gradient(45deg,transparent 0 36px,rgba(251,146,60,.9) 36px 45px),repeating-linear-gradient(-45deg,transparent 0 48px,rgba(239,68,68,.78) 48px 57px),radial-gradient(circle at center,#7c2d12,#1c0704 70%)}.page-theme-starfield{background-color:#020617;background-image:radial-gradient(circle,rgba(255,255,255,.95) 0 2px,transparent 3px),radial-gradient(circle,rgba(125,211,252,.9) 0 3px,transparent 4px),radial-gradient(circle,rgba(216,180,254,.8) 0 2px,transparent 3px),linear-gradient(135deg,#020617,#172554 55%,#3b0764);background-position:0 0,57px 81px,123px 33px,0 0;background-size:111px 111px,183px 183px,237px 237px,auto}.page-theme-vaporwave-sunset{background-color:#2e1065;background-image:repeating-linear-gradient(0deg,transparent 0 24px,rgba(103,232,249,.48) 24px 27px,transparent 27px 54px),radial-gradient(circle at 50% 78%,#fda4af 0 14%,#f97316 14% 25%,transparent 25.5%),linear-gradient(180deg,#172554,#6b21a8 48%,#db2777 72%,#f97316);background-size:auto,48rem 34rem,auto}.page-theme-electric-circuit{background-color:#020617;background-image:repeating-linear-gradient(90deg,transparent 0 48px,rgba(34,211,238,.9) 48px 52px,transparent 52px 96px),repeating-linear-gradient(0deg,transparent 0 68px,rgba(168,85,247,.86) 68px 72px,transparent 72px 124px),radial-gradient(circle at center,#312e81 0,#0f172a 45%,#020617 78%)}.page-theme-lava-flow{background-color:#180503;background-image:radial-gradient(ellipse at 18% 28%,#facc15 0 8%,#f97316 9% 18%,#991b1b 24%,transparent 39%),radial-gradient(ellipse at 76% 64%,#fb923c 0 10%,#dc2626 18%,#7f1d1d 29%,transparent 44%),radial-gradient(ellipse at 48% 92%,#fde047 0 7%,#ea580c 17%,transparent 36%),linear-gradient(135deg,#180503,#450a0a 50%,#1c0704);background-size:42rem 34rem,48rem 39rem,37rem 31rem,auto}.page-theme-deep-space-nebula{background-color:#020617;background-image:radial-gradient(ellipse at 22% 38%,rgba(217,70,239,.92),transparent 34%),radial-gradient(ellipse at 72% 24%,rgba(59,130,246,.9),transparent 38%),radial-gradient(ellipse at 58% 82%,rgba(14,165,233,.72),transparent 36%),radial-gradient(circle,rgba(255,255,255,.9) 0 2px,transparent 3px),linear-gradient(125deg,#020617,#1e1b4b 52%,#3b0764);background-position:center,center,center,17px 31px,center;background-size:42rem 34rem,48rem 38rem,39rem 35rem,173px 173px,auto}.page-theme-holographic-waves{background-color:#164e63;background-image:repeating-linear-gradient(32deg,rgba(255,255,255,.4) 0 14px,transparent 14px 82px),repeating-linear-gradient(-38deg,rgba(216,180,254,.38) 0 18px,transparent 18px 104px),linear-gradient(110deg,#67e8f9,#c4b5fd 32%,#f9a8d4 60%,#86efac)}.page-theme-hazard-stripes{background-color:#111;background-image:repeating-linear-gradient(45deg,#facc15 0 48px,#171717 48px 96px)}.page-theme-mint-mosaic{background-color:#064e3b;background-image:conic-gradient(from 45deg,#a7f3d0 0 25%,#0f766e 0 50%,#fef3c7 0 75%,#164e63 0);background-size:96px 96px}.page-theme-candy-swirl{background-color:#fdf2f8;background-image:repeating-radial-gradient(circle at 28% 34%,#f9a8d4 0 28px,#fef3c7 28px 56px,#93c5fd 56px 84px,#c4b5fd 84px 112px,#f9a8d4 112px 140px)}.inter-instance-overlay-frame{max-width:calc(100vw - .5rem)}.inter-instance-overlay-body{max-height:82vh}@media(min-width:1024px){.inter-instance-overlay-scale{zoom:1.5}.inter-instance-overlay-frame{max-width:calc((100vw - .5rem) / 1.5)}.inter-instance-overlay-body{max-height:54.6667vh}}.chat-composer{container-type:inline-size;display:flex;flex-wrap:wrap;align-items:stretch;gap:.125rem;min-width:0;overflow:hidden}.chat-composer-nickname{flex:1 1 100%;min-width:0;order:1}.chat-composer-input{flex:1 1 0%;min-width:0;order:2}.chat-composer-send{flex:0 0 auto;align-self:stretch;order:3}.chat-composer-tts{display:flex;flex:1 1 100%;align-items:center;gap:.125rem;min-width:0;overflow:hidden;order:4}@container (min-width: 44rem){.chat-composer{flex-wrap:nowrap}.chat-composer-nickname{flex:0 0 7rem;order:1}.chat-composer-input{order:2}.chat-composer-tts{flex:0 1 auto;order:3}.chat-composer-send{order:4}}.mobile-text-entry{font-size:16px;-webkit-text-size-adjust:100%;touch-action:manipulation}.no-touch-select{-moz-user-select:none;user-select:none;-webkit-user-select:none;-ms-user-select:none;-webkit-touch-callout:none}.mobile-touch-control{user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;overscroll-behavior:contain}.mobile-drag-control{touch-action:none}.newgen-wheel-drop{pointer-events:none;position:absolute;z-index:20;top:50%;display:flex;width:4rem;height:15rem;align-items:center;justify-content:center;border-radius:.25rem;background:#ef44445c;color:#fff;transform:translateY(-50%)}.newgen-wheel-drop--left{left:1rem}.newgen-wheel-drop--right{right:1rem}.newgen-wheel-drop span{white-space:nowrap;font-size:.9rem;font-weight:700;letter-spacing:.08em;line-height:1;text-shadow:0 1px 2px rgb(0 0 0 / .65)}.newgen-wheel-drop--left span{transform:rotate(-90deg)}.newgen-wheel-drop--right span{transform:rotate(90deg)}.newgen-bumper-path{fill:none;stroke:#ef44447a;stroke-width:26;stroke-linecap:round;vector-effect:non-scaling-stroke}.newgen-bumper-label{fill:#fff;font-family:system-ui,sans-serif;font-size:20px;font-weight:700;letter-spacing:1px;paint-order:stroke;stroke:#000000a6;stroke-width:1.5px}.newgen-ir-proximity-cone{fill:none;stroke-width:6;stroke-linecap:round;vector-effect:non-scaling-stroke;transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.newgen-ir-proximity-cone{transition:none}}.newgen-cliff-path{fill:none;stroke:#f59e0bad;stroke-width:22;stroke-linecap:round;vector-effect:non-scaling-stroke}.newgen-cliff-label{fill:#fff;font-family:system-ui,sans-serif;font-size:15px;font-weight:700;letter-spacing:1px;paint-order:stroke;stroke:#000000a6;stroke-width:1.5px}.newgen-bottom-sensor-hud{pointer-events:none;position:absolute;z-index:20;right:0;bottom:-2.5rem;left:0;width:100%;height:clamp(7rem,18vh,11rem);overflow:visible}@keyframes batteryTickWarn{0%,49%{background-color:#fffffff2}50%,to{background-color:#ef4444f2}}.battery-tick-warn{animation:batteryTickWarn .4s steps(2,end) infinite}@keyframes batteryUrgentFlash{0%,49%{opacity:1}50%,to{opacity:.25}}.battery-urgent-flash{animation:batteryUrgentFlash .4s steps(2,end) infinite}@media(max-width:1023px){.driver-mobile-layout{--mobile-landscape-video-width: min(60vw, 133.333svh)}.driver-mobile-layout:has(>.panel-section){--mobile-landscape-video-width: min(60vw, calc(133.333svh - 3.333rem) )}.driver-mobile-layout .mobile-landscape-driver-grid{grid-template-columns:minmax(0,1fr) minmax(0,var(--mobile-landscape-video-width)) minmax(0,1fr)}html:has(.driver-mobile-layout){scroll-snap-type:y mandatory}.driver-mobile-layout .mobile-top-snap{position:absolute;top:0;left:0;width:1px;height:1px;pointer-events:none;scroll-snap-align:start;scroll-snap-stop:always}.driver-mobile-layout .mobile-content-snap,.driver-mobile-layout .mobile-tabs-snap{scroll-snap-align:start;scroll-snap-stop:always}}@keyframes initial-session-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes initial-session-spinner-hue{0%{filter:hue-rotate(0deg)}to{filter:hue-rotate(360deg)}}@media(prefers-reduced-motion:no-preference){.initial-session-spinner-rotation{animation:initial-session-spinner-rotate 1.8s linear infinite}.initial-session-spinner-image{animation:initial-session-spinner-hue 2.8s linear infinite}} diff --git a/server/public/assets/index-BZ2ymoHR.js b/server/public/assets/index-BZ2ymoHR.js deleted file mode 100644 index efd78d08..00000000 --- a/server/public/assets/index-BZ2ymoHR.js +++ /dev/null @@ -1,148 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/OrbitControls-BJ-UXxdt.js","assets/three.module--MGUDD-H.js"])))=>i.map(i=>d[i]); -(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))l(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const h of o.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&l(h)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function l(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();const xx="modulepreload",gx=function(t){return"/"+t},Ho={},R9=function(c,r,l){let s=Promise.resolve();if(r&&r.length>0){let v=function(g){return Promise.all(g.map(x=>Promise.resolve(x).then(z=>({status:"fulfilled",value:z}),z=>({status:"rejected",reason:z}))))};document.getElementsByTagName("link");const h=document.querySelector("meta[property=csp-nonce]"),m=h?.nonce||h?.getAttribute("nonce");s=v(r.map(g=>{if(g=gx(g),g in Ho)return;Ho[g]=!0;const x=g.endsWith(".css"),z=x?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${g}"]${z}`))return;const y=document.createElement("link");if(y.rel=x?"stylesheet":xx,x||(y.as="script"),y.crossOrigin="",y.href=g,m&&y.setAttribute("nonce",m),document.head.appendChild(y),x)return new Promise((w,L)=>{y.addEventListener("load",w),y.addEventListener("error",()=>L(new Error(`Unable to preload CSS for ${g}`)))})}))}function o(h){const m=new Event("vite:preloadError",{cancelable:!0});if(m.payload=h,window.dispatchEvent(m),!m.defaultPrevented)throw h}return s.then(h=>{for(const m of h||[])m.status==="rejected"&&o(m.reason);return c().catch(o)})};function bx(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var qn={exports:{}},ae={};var Lo;function zx(){if(Lo)return ae;Lo=1;var t=Symbol.for("react.transitional.element"),c=Symbol.for("react.fragment");function r(l,s,o){var h=null;if(o!==void 0&&(h=""+o),s.key!==void 0&&(h=""+s.key),"key"in s){o={};for(var m in s)m!=="key"&&(o[m]=s[m])}else o=s;return s=o.ref,{$$typeof:t,type:l,key:h,ref:s!==void 0?s:null,props:o}}return ae.Fragment=c,ae.jsx=r,ae.jsxs=r,ae}var jo;function yx(){return jo||(jo=1,qn.exports=zx()),qn.exports}var a=yx(),_n={exports:{}},I1={};var No;function wx(){if(No)return I1;No=1;var t=Symbol.for("react.transitional.element"),c=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),l=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),h=Symbol.for("react.context"),m=Symbol.for("react.forward_ref"),v=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),x=Symbol.for("react.lazy"),z=Symbol.for("react.activity"),y=Symbol.iterator;function w(D){return D===null||typeof D!="object"?null:(D=y&&D[y]||D["@@iterator"],typeof D=="function"?D:null)}var L={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},H=Object.assign,M={};function V(D,O,Y){this.props=D,this.context=O,this.refs=M,this.updater=Y||L}V.prototype.isReactComponent={},V.prototype.setState=function(D,O){if(typeof D!="object"&&typeof D!="function"&&D!=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,D,O,"setState")},V.prototype.forceUpdate=function(D){this.updater.enqueueForceUpdate(this,D,"forceUpdate")};function C(){}C.prototype=V.prototype;function S(D,O,Y){this.props=D,this.context=O,this.refs=M,this.updater=Y||L}var j=S.prototype=new C;j.constructor=S,H(j,V.prototype),j.isPureReactComponent=!0;var E=Array.isArray;function k(){}var R={H:null,A:null,T:null,S:null},N=Object.prototype.hasOwnProperty;function B(D,O,Y){var W=Y.ref;return{$$typeof:t,type:D,key:O,ref:W!==void 0?W:null,props:Y}}function _(D,O){return B(D.type,O,D.props)}function q(D){return typeof D=="object"&&D!==null&&D.$$typeof===t}function P(D){var O={"=":"=0",":":"=2"};return"$"+D.replace(/[=:]/g,function(Y){return O[Y]})}var J=/\/+/g;function K(D,O){return typeof D=="object"&&D!==null&&D.key!=null?P(""+D.key):O.toString(36)}function Z(D){switch(D.status){case"fulfilled":return D.value;case"rejected":throw D.reason;default:switch(typeof D.status=="string"?D.then(k,k):(D.status="pending",D.then(function(O){D.status==="pending"&&(D.status="fulfilled",D.value=O)},function(O){D.status==="pending"&&(D.status="rejected",D.reason=O)})),D.status){case"fulfilled":return D.value;case"rejected":throw D.reason}}throw D}function T(D,O,Y,W,t1){var i1=typeof D;(i1==="undefined"||i1==="boolean")&&(D=null);var a1=!1;if(D===null)a1=!0;else switch(i1){case"bigint":case"string":case"number":a1=!0;break;case"object":switch(D.$$typeof){case t:case c:a1=!0;break;case x:return a1=D._init,T(a1(D._payload),O,Y,W,t1)}}if(a1)return t1=t1(D),a1=W===""?"."+K(D,0):W,E(t1)?(Y="",a1!=null&&(Y=a1.replace(J,"$&/")+"/"),T(t1,O,Y,"",function(N1){return N1})):t1!=null&&(q(t1)&&(t1=_(t1,Y+(t1.key==null||D&&D.key===t1.key?"":(""+t1.key).replace(J,"$&/")+"/")+a1)),O.push(t1)),1;a1=0;var y1=W===""?".":W+":";if(E(D))for(var b1=0;b1>>1,m1=T[n1];if(0>>1;n1s(Y,G))Ws(t1,Y)?(T[n1]=t1,T[W]=G,n1=W):(T[n1]=Y,T[O]=G,n1=O);else if(Ws(t1,G))T[n1]=t1,T[W]=G,n1=W;else break t}}return I}function s(T,I){var G=T.sortIndex-I.sortIndex;return G!==0?G:T.id-I.id}if(t.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;t.unstable_now=function(){return o.now()}}else{var h=Date,m=h.now();t.unstable_now=function(){return h.now()-m}}var v=[],g=[],x=1,z=null,y=3,w=!1,L=!1,H=!1,M=!1,V=typeof setTimeout=="function"?setTimeout:null,C=typeof clearTimeout=="function"?clearTimeout:null,S=typeof setImmediate<"u"?setImmediate:null;function j(T){for(var I=r(g);I!==null;){if(I.callback===null)l(g);else if(I.startTime<=T)l(g),I.sortIndex=I.expirationTime,c(v,I);else break;I=r(g)}}function E(T){if(H=!1,j(T),!L)if(r(v)!==null)L=!0,k||(k=!0,P());else{var I=r(g);I!==null&&Z(E,I.startTime-T)}}var k=!1,R=-1,N=5,B=-1;function _(){return M?!0:!(t.unstable_now()-BT&&_());){var n1=z.callback;if(typeof n1=="function"){z.callback=null,y=z.priorityLevel;var m1=n1(z.expirationTime<=T);if(T=t.unstable_now(),typeof m1=="function"){z.callback=m1,j(T),I=!0;break e}z===r(v)&&l(v),j(T)}else l(v);z=r(v)}if(z!==null)I=!0;else{var D=r(g);D!==null&&Z(E,D.startTime-T),I=!1}}break t}finally{z=null,y=G,w=!1}I=void 0}}finally{I?P():k=!1}}}var P;if(typeof S=="function")P=function(){S(q)};else if(typeof MessageChannel<"u"){var J=new MessageChannel,K=J.port2;J.port1.onmessage=q,P=function(){K.postMessage(null)}}else P=function(){V(q,0)};function Z(T,I){R=V(function(){T(t.unstable_now())},I)}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(T){T.callback=null},t.unstable_forceFrameRate=function(T){0>T||125n1?(T.sortIndex=G,c(g,T),r(v)===null&&T===r(g)&&(H?(C(R),R=-1):H=!0,Z(E,G-n1))):(T.sortIndex=m1,c(v,T),L||w||(L=!0,k||(k=!0,P()))),T},t.unstable_shouldYield=_,t.unstable_wrapCallback=function(T){var I=y;return function(){var G=y;y=I;try{return T.apply(this,arguments)}finally{y=G}}}})(Pn)),Pn}var ko;function Cx(){return ko||(ko=1,Un.exports=Mx()),Un.exports}var In={exports:{}},e4={};var Fo;function Sx(){if(Fo)return e4;Fo=1;var t=Aa();function c(v){var g="https://react.dev/errors/"+v;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(c){console.error(c)}}return t(),In.exports=Sx(),In.exports}var Eo;function Ax(){if(Eo)return re;Eo=1;var t=Cx(),c=Aa(),r=Yd();function l(e){var n="https://react.dev/errors/"+e;if(1m1||(e.current=n1[m1],n1[m1]=null,m1--)}function Y(e,n){m1++,n1[m1]=e.current,e.current=n}var W=D(null),t1=D(null),i1=D(null),a1=D(null);function y1(e,n){switch(Y(i1,n),Y(t1,e),Y(W,null),n.nodeType){case 9:case 11:e=(e=n.documentElement)&&(e=e.namespaceURI)?Ks(e):0;break;default:if(e=n.tagName,n=n.namespaceURI)n=Ks(n),e=Qs(n,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}O(W),Y(W,e)}function b1(){O(W),O(t1),O(i1)}function N1(e){e.memoizedState!==null&&Y(a1,e);var n=W.current,i=Qs(n,e.type);n!==i&&(Y(t1,e),Y(W,i))}function x1(e){t1.current===e&&(O(W),O(t1)),a1.current===e&&(O(a1),te._currentValue=G)}var M1,P1;function T1(e){if(M1===void 0)try{throw Error()}catch(i){var n=i.stack.trim().match(/\n( *(at )?)/);M1=n&&n[1]||"",P1=-1)":-1p||U[d]!==c1[p]){var u1=` -`+U[d].replace(" at new "," at ");return e.displayName&&u1.includes("")&&(u1=u1.replace("",e.displayName)),u1}while(1<=d&&0<=p);break}}}finally{d2=!1,Error.prepareStackTrace=i}return(i=e?e.displayName||e.name:"")?T1(i):""}function j1(e,n){switch(e.tag){case 26:case 27:case 5:return T1(e.type);case 16:return T1("Lazy");case 13:return e.child!==n&&n!==null?T1("Suspense Fallback"):T1("Suspense");case 19:return T1("SuspenseList");case 0:case 15:return V1(e.type,!1);case 11:return V1(e.type.render,!1);case 1:return V1(e.type,!0);case 31:return T1("Activity");default:return""}}function G1(e){try{var n="",i=null;do n+=j1(e,i),i=e,e=e.return;while(e);return n}catch(d){return` -Error generating stack: `+d.message+` -`+d.stack}}var Y1=Object.prototype.hasOwnProperty,S1=t.unstable_scheduleCallback,U1=t.unstable_cancelCallback,p1=t.unstable_shouldYield,A1=t.unstable_requestPaint,l1=t.unstable_now,z1=t.unstable_getCurrentPriorityLevel,g1=t.unstable_ImmediatePriority,C1=t.unstable_UserBlockingPriority,$1=t.unstable_NormalPriority,o1=t.unstable_LowPriority,h1=t.unstable_IdlePriority,R1=t.log,J1=t.unstable_setDisableYieldValue,b2=null,H1=null;function N2(e){if(typeof R1=="function"&&J1(e),H1&&typeof H1.setStrictMode=="function")try{H1.setStrictMode(b2,e)}catch{}}var V2=Math.clz32?Math.clz32:q1,v1=Math.log,_1=Math.LN2;function q1(e){return e>>>=0,e===0?32:31-(v1(e)/_1|0)|0}var Z2=256,J4=262144,G0=4194304;function F1(e){var n=e&42;if(n!==0)return n;switch(e&-e){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 e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function h2(e,n,i){var d=e.pendingLanes;if(d===0)return 0;var p=0,b=e.suspendedLanes,A=e.pingedLanes;e=e.warmLanes;var F=d&134217727;return F!==0?(d=F&~b,d!==0?p=F1(d):(A&=F,A!==0?p=F1(A):i||(i=F&~e,i!==0&&(p=F1(i))))):(F=d&~b,F!==0?p=F1(F):A!==0?p=F1(A):i||(i=d&~e,i!==0&&(p=F1(i)))),p===0?0:n!==0&&n!==p&&(n&b)===0&&(b=p&-p,i=n&-n,b>=i||b===32&&(i&4194048)!==0)?n:p}function s2(e,n){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&n)===0}function n2(e,n){switch(e){case 1:case 2:case 4:case 8:case 64:return n+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 n+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 x2(){var e=G0;return G0<<=1,(G0&62914560)===0&&(G0=4194304),e}function m4(e){for(var n=[],i=0;31>i;i++)n.push(e);return n}function O4(e,n){e.pendingLanes|=n,n!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Y0(e,n,i,d,p,b){var A=e.pendingLanes;e.pendingLanes=i,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=i,e.entangledLanes&=i,e.errorRecoveryDisabledLanes&=i,e.shellSuspendCounter=0;var F=e.entanglements,U=e.expirationTimes,c1=e.hiddenUpdates;for(i=A&~i;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var dv=/[\n"\\]/g;function A4(e){return e.replace(dv,function(n){return"\\"+n.charCodeAt(0).toString(16)+" "})}function B7(e,n,i,d,p,b,A,F){e.name="",A!=null&&typeof A!="function"&&typeof A!="symbol"&&typeof A!="boolean"?e.type=A:e.removeAttribute("type"),n!=null?A==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+S4(n)):e.value!==""+S4(n)&&(e.value=""+S4(n)):A!=="submit"&&A!=="reset"||e.removeAttribute("value"),n!=null?k7(e,A,S4(n)):i!=null?k7(e,A,S4(i)):d!=null&&e.removeAttribute("value"),p==null&&b!=null&&(e.defaultChecked=!!b),p!=null&&(e.checked=p&&typeof p!="function"&&typeof p!="symbol"),F!=null&&typeof F!="function"&&typeof F!="symbol"&&typeof F!="boolean"?e.name=""+S4(F):e.removeAttribute("name")}function Pr(e,n,i,d,p,b,A,F){if(b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"&&(e.type=b),n!=null||i!=null){if(!(b!=="submit"&&b!=="reset"||n!=null)){V7(e);return}i=i!=null?""+S4(i):"",n=n!=null?""+S4(n):i,F||n===e.value||(e.value=n),e.defaultValue=n}d=d??p,d=typeof d!="function"&&typeof d!="symbol"&&!!d,e.checked=F?e.checked:!!d,e.defaultChecked=!!d,A!=null&&typeof A!="function"&&typeof A!="symbol"&&typeof A!="boolean"&&(e.name=A),V7(e)}function k7(e,n,i){n==="number"&&Qe(e.ownerDocument)===e||e.defaultValue===""+i||(e.defaultValue=""+i)}function t6(e,n,i,d){if(e=e.options,n){n={};for(var p=0;p"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),D7=!1;if(h3)try{var gt={};Object.defineProperty(gt,"passive",{get:function(){D7=!0}}),window.addEventListener("test",gt,gt),window.removeEventListener("test",gt,gt)}catch{D7=!1}var T3=null,q7=null,We=null;function Qr(){if(We)return We;var e,n=q7,i=n.length,d,p="value"in T3?T3.value:T3.textContent,b=p.length;for(e=0;e=yt),cl=" ",nl=!1;function al(e,n){switch(e){case"keyup":return qv.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function rl(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var a6=!1;function Ov(e,n){switch(e){case"compositionend":return rl(n);case"keypress":return n.which!==32?null:(nl=!0,cl);case"textInput":return e=n.data,e===cl&&nl?null:e;default:return null}}function Uv(e,n){if(a6)return e==="compositionend"||!I7&&al(e,n)?(e=Qr(),We=q7=T3=null,a6=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:i,offset:n-e};e=d}t:{for(;i;){if(i.nextSibling){i=i.nextSibling;break t}i=i.parentNode}i=void 0}i=fl(i)}}function vl(e,n){return e&&n?e===n?!0:e&&e.nodeType===3?!1:n&&n.nodeType===3?vl(e,n.parentNode):"contains"in e?e.contains(n):e.compareDocumentPosition?!!(e.compareDocumentPosition(n)&16):!1:!1}function pl(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var n=Qe(e.document);n instanceof e.HTMLIFrameElement;){try{var i=typeof n.contentWindow.location.href=="string"}catch{i=!1}if(i)e=n.contentWindow;else break;n=Qe(e.document)}return n}function G7(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&(n==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||n==="textarea"||e.contentEditable==="true")}var Qv=h3&&"documentMode"in document&&11>=document.documentMode,r6=null,Y7=null,St=null,K7=!1;function xl(e,n,i){var d=i.window===i?i.document:i.nodeType===9?i:i.ownerDocument;K7||r6==null||r6!==Qe(d)||(d=r6,"selectionStart"in d&&G7(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d={anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),St&&Ct(St,d)||(St=d,d=$8(Y7,"onSelect"),0>=A,p-=A,t3=1<<32-V2(n)+p|i<K1?(e2=k1,k1=null):e2=k1.sibling;var r2=r1(X,k1,e1[K1],d1);if(r2===null){k1===null&&(k1=e2);break}e&&k1&&r2.alternate===null&&n(X,k1),$=b(r2,$,K1),a2===null?D1=r2:a2.sibling=r2,a2=r2,k1=e2}if(K1===e1.length)return i(X,k1),c2&&m3(X,K1),D1;if(k1===null){for(;K1K1?(e2=k1,k1=null):e2=k1.sibling;var r0=r1(X,k1,r2.value,d1);if(r0===null){k1===null&&(k1=e2);break}e&&k1&&r0.alternate===null&&n(X,k1),$=b(r0,$,K1),a2===null?D1=r0:a2.sibling=r0,a2=r0,k1=e2}if(r2.done)return i(X,k1),c2&&m3(X,K1),D1;if(k1===null){for(;!r2.done;K1++,r2=e1.next())r2=f1(X,r2.value,d1),r2!==null&&($=b(r2,$,K1),a2===null?D1=r2:a2.sibling=r2,a2=r2);return c2&&m3(X,K1),D1}for(k1=d(k1);!r2.done;K1++,r2=e1.next())r2=s1(k1,X,K1,r2.value,d1),r2!==null&&(e&&r2.alternate!==null&&k1.delete(r2.key===null?K1:r2.key),$=b(r2,$,K1),a2===null?D1=r2:a2.sibling=r2,a2=r2);return e&&k1.forEach(function(px){return n(X,px)}),c2&&m3(X,K1),D1}function v2(X,$,e1,d1){if(typeof e1=="object"&&e1!==null&&e1.type===H&&e1.key===null&&(e1=e1.props.children),typeof e1=="object"&&e1!==null){switch(e1.$$typeof){case w:t:{for(var D1=e1.key;$!==null;){if($.key===D1){if(D1=e1.type,D1===H){if($.tag===7){i(X,$.sibling),d1=p($,e1.props.children),d1.return=X,X=d1;break t}}else if($.elementType===D1||typeof D1=="object"&&D1!==null&&D1.$$typeof===N&&A0(D1)===$.type){i(X,$.sibling),d1=p($,e1.props),Vt(d1,e1),d1.return=X,X=d1;break t}i(X,$);break}else n(X,$);$=$.sibling}e1.type===H?(d1=y0(e1.props.children,X.mode,d1,e1.key),d1.return=X,X=d1):(d1=s8(e1.type,e1.key,e1.props,null,X.mode,d1),Vt(d1,e1),d1.return=X,X=d1)}return A(X);case L:t:{for(D1=e1.key;$!==null;){if($.key===D1)if($.tag===4&&$.stateNode.containerInfo===e1.containerInfo&&$.stateNode.implementation===e1.implementation){i(X,$.sibling),d1=p($,e1.children||[]),d1.return=X,X=d1;break t}else{i(X,$);break}else n(X,$);$=$.sibling}d1=cc(e1,X.mode,d1),d1.return=X,X=d1}return A(X);case N:return e1=A0(e1),v2(X,$,e1,d1)}if(Z(e1))return L1(X,$,e1,d1);if(P(e1)){if(D1=P(e1),typeof D1!="function")throw Error(l(150));return e1=D1.call(e1),O1(X,$,e1,d1)}if(typeof e1.then=="function")return v2(X,$,v8(e1),d1);if(e1.$$typeof===S)return v2(X,$,d8(X,e1),d1);p8(X,e1)}return typeof e1=="string"&&e1!==""||typeof e1=="number"||typeof e1=="bigint"?(e1=""+e1,$!==null&&$.tag===6?(i(X,$.sibling),d1=p($,e1),d1.return=X,X=d1):(i(X,$),d1=ec(e1,X.mode,d1),d1.return=X,X=d1),A(X)):i(X,$)}return function(X,$,e1,d1){try{Nt=0;var D1=v2(X,$,e1,d1);return p6=null,D1}catch(k1){if(k1===v6||k1===f8)throw k1;var a2=p4(29,k1,null,X.mode);return a2.lanes=d1,a2.return=X,a2}finally{}}}var L0=_l(!0),Ol=_l(!1),U3=!1;function mc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function vc(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function P3(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function I3(e,n,i){var d=e.updateQueue;if(d===null)return null;if(d=d.shared,(i2&2)!==0){var p=d.pending;return p===null?n.next=n:(n.next=p.next,p.next=n),d.pending=n,n=i8(e),Cl(e,null,i),n}return l8(e,d,n,i),i8(e)}function Bt(e,n,i){if(n=n.updateQueue,n!==null&&(n=n.shared,(i&4194048)!==0)){var d=n.lanes;d&=e.pendingLanes,i|=d,n.lanes=i,R3(e,i)}}function pc(e,n){var i=e.updateQueue,d=e.alternate;if(d!==null&&(d=d.updateQueue,i===d)){var p=null,b=null;if(i=i.firstBaseUpdate,i!==null){do{var A={lane:i.lane,tag:i.tag,payload:i.payload,callback:null,next:null};b===null?p=b=A:b=b.next=A,i=i.next}while(i!==null);b===null?p=b=n:b=b.next=n}else p=b=n;i={baseState:d.baseState,firstBaseUpdate:p,lastBaseUpdate:b,shared:d.shared,callbacks:d.callbacks},e.updateQueue=i;return}e=i.lastBaseUpdate,e===null?i.firstBaseUpdate=n:e.next=n,i.lastBaseUpdate=n}var xc=!1;function kt(){if(xc){var e=m6;if(e!==null)throw e}}function Ft(e,n,i,d){xc=!1;var p=e.updateQueue;U3=!1;var b=p.firstBaseUpdate,A=p.lastBaseUpdate,F=p.shared.pending;if(F!==null){p.shared.pending=null;var U=F,c1=U.next;U.next=null,A===null?b=c1:A.next=c1,A=U;var u1=e.alternate;u1!==null&&(u1=u1.updateQueue,F=u1.lastBaseUpdate,F!==A&&(F===null?u1.firstBaseUpdate=c1:F.next=c1,u1.lastBaseUpdate=U))}if(b!==null){var f1=p.baseState;A=0,u1=c1=U=null,F=b;do{var r1=F.lane&-536870913,s1=r1!==F.lane;if(s1?(t2&r1)===r1:(d&r1)===r1){r1!==0&&r1===f6&&(xc=!0),u1!==null&&(u1=u1.next={lane:0,tag:F.tag,payload:F.payload,callback:null,next:null});t:{var L1=e,O1=F;r1=n;var v2=i;switch(O1.tag){case 1:if(L1=O1.payload,typeof L1=="function"){f1=L1.call(v2,f1,r1);break t}f1=L1;break t;case 3:L1.flags=L1.flags&-65537|128;case 0:if(L1=O1.payload,r1=typeof L1=="function"?L1.call(v2,f1,r1):L1,r1==null)break t;f1=z({},f1,r1);break t;case 2:U3=!0}}r1=F.callback,r1!==null&&(e.flags|=64,s1&&(e.flags|=8192),s1=p.callbacks,s1===null?p.callbacks=[r1]:s1.push(r1))}else s1={lane:r1,tag:F.tag,payload:F.payload,callback:F.callback,next:null},u1===null?(c1=u1=s1,U=f1):u1=u1.next=s1,A|=r1;if(F=F.next,F===null){if(F=p.shared.pending,F===null)break;s1=F,F=s1.next,s1.next=null,p.lastBaseUpdate=s1,p.shared.pending=null}}while(!0);u1===null&&(U=f1),p.baseState=U,p.firstBaseUpdate=c1,p.lastBaseUpdate=u1,b===null&&(p.shared.lanes=0),K3|=A,e.lanes=A,e.memoizedState=f1}}function Ul(e,n){if(typeof e!="function")throw Error(l(191,e));e.call(n)}function Pl(e,n){var i=e.callbacks;if(i!==null)for(e.callbacks=null,e=0;eb?b:8;var A=T.T,F={};T.T=F,Ec(e,!1,n,i);try{var U=p(),c1=T.S;if(c1!==null&&c1(F,U),U!==null&&typeof U=="object"&&typeof U.then=="function"){var u1=rp(U,d);Tt(e,n,u1,y4(e))}else Tt(e,n,d,y4(e))}catch(f1){Tt(e,n,{then:function(){},status:"rejected",reason:f1},y4())}finally{I.p=b,A!==null&&F.types!==null&&(A.types=F.types),T.T=A}}function dp(){}function Fc(e,n,i,d){if(e.tag!==5)throw Error(l(476));var p=yi(e).queue;zi(e,p,n,G,i===null?dp:function(){return wi(e),i(d)})}function yi(e){var n=e.memoizedState;if(n!==null)return n;n={memoizedState:G,baseState:G,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:g3,lastRenderedState:G},next:null};var i={};return n.next={memoizedState:i,baseState:i,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:g3,lastRenderedState:i},next:null},e.memoizedState=n,e=e.alternate,e!==null&&(e.memoizedState=n),n}function wi(e){var n=yi(e);n.next===null&&(n=e.alternate.memoizedState),Tt(e,n.next.queue,{},y4())}function Rc(){return W2(te)}function Mi(){return R2().memoizedState}function Ci(){return R2().memoizedState}function hp(e){for(var n=e.return;n!==null;){switch(n.tag){case 24:case 3:var i=y4();e=P3(i);var d=I3(n,e,i);d!==null&&(d4(d,n,i),Bt(d,n,i)),n={cache:uc()},e.payload=n;return}n=n.return}}function fp(e,n,i){var d=y4();i={lane:d,revertLane:0,gesture:null,action:i,hasEagerState:!1,eagerState:null,next:null},A8(e)?Ai(n,i):(i=J7(e,n,i,d),i!==null&&(d4(i,e,d),Hi(i,n,d)))}function Si(e,n,i){var d=y4();Tt(e,n,i,d)}function Tt(e,n,i,d){var p={lane:d,revertLane:0,gesture:null,action:i,hasEagerState:!1,eagerState:null,next:null};if(A8(e))Ai(n,p);else{var b=e.alternate;if(e.lanes===0&&(b===null||b.lanes===0)&&(b=n.lastRenderedReducer,b!==null))try{var A=n.lastRenderedState,F=b(A,i);if(p.hasEagerState=!0,p.eagerState=F,v4(F,A))return l8(e,n,p,0),g2===null&&r8(),!1}catch{}finally{}if(i=J7(e,n,p,d),i!==null)return d4(i,e,d),Hi(i,n,d),!0}return!1}function Ec(e,n,i,d){if(d={lane:2,revertLane:vn(),gesture:null,action:d,hasEagerState:!1,eagerState:null,next:null},A8(e)){if(n)throw Error(l(479))}else n=J7(e,i,d,2),n!==null&&d4(n,e,2)}function A8(e){var n=e.alternate;return e===Z1||n!==null&&n===Z1}function Ai(e,n){g6=b8=!0;var i=e.pending;i===null?n.next=n:(n.next=i.next,i.next=n),e.pending=n}function Hi(e,n,i){if((i&4194048)!==0){var d=n.lanes;d&=e.pendingLanes,i|=d,n.lanes=i,R3(e,i)}}var Dt={readContext:W2,use:w8,useCallback:B2,useContext:B2,useEffect:B2,useImperativeHandle:B2,useLayoutEffect:B2,useInsertionEffect:B2,useMemo:B2,useReducer:B2,useRef:B2,useState:B2,useDebugValue:B2,useDeferredValue:B2,useTransition:B2,useSyncExternalStore:B2,useId:B2,useHostTransitionStatus:B2,useFormState:B2,useActionState:B2,useOptimistic:B2,useMemoCache:B2,useCacheRefresh:B2};Dt.useEffectEvent=B2;var Li={readContext:W2,use:w8,useCallback:function(e,n){return n4().memoizedState=[e,n===void 0?null:n],e},useContext:W2,useEffect:di,useImperativeHandle:function(e,n,i){i=i!=null?i.concat([e]):null,C8(4194308,4,vi.bind(null,n,e),i)},useLayoutEffect:function(e,n){return C8(4194308,4,e,n)},useInsertionEffect:function(e,n){C8(4,2,e,n)},useMemo:function(e,n){var i=n4();n=n===void 0?null:n;var d=e();if(j0){N2(!0);try{e()}finally{N2(!1)}}return i.memoizedState=[d,n],d},useReducer:function(e,n,i){var d=n4();if(i!==void 0){var p=i(n);if(j0){N2(!0);try{i(n)}finally{N2(!1)}}}else p=n;return d.memoizedState=d.baseState=p,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:p},d.queue=e,e=e.dispatch=fp.bind(null,Z1,e),[d.memoizedState,e]},useRef:function(e){var n=n4();return e={current:e},n.memoizedState=e},useState:function(e){e=jc(e);var n=e.queue,i=Si.bind(null,Z1,n);return n.dispatch=i,[e.memoizedState,i]},useDebugValue:Bc,useDeferredValue:function(e,n){var i=n4();return kc(i,e,n)},useTransition:function(){var e=jc(!1);return e=zi.bind(null,Z1,e.queue,!0,!1),n4().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,n,i){var d=Z1,p=n4();if(c2){if(i===void 0)throw Error(l(407));i=i()}else{if(i=n(),g2===null)throw Error(l(349));(t2&127)!==0||Kl(d,n,i)}p.memoizedState=i;var b={value:i,getSnapshot:n};return p.queue=b,di(Xl.bind(null,d,b,e),[e]),d.flags|=2048,z6(9,{destroy:void 0},Ql.bind(null,d,b,i,n),null),i},useId:function(){var e=n4(),n=g2.identifierPrefix;if(c2){var i=e3,d=t3;i=(d&~(1<<32-V2(d)-1)).toString(32)+i,n="_"+n+"R_"+i,i=z8++,0<\/script>",b=b.removeChild(b.firstChild);break;case"select":b=typeof d.is=="string"?A.createElement("select",{is:d.is}):A.createElement("select"),d.multiple?b.multiple=!0:d.size&&(b.size=d.size);break;default:b=typeof d.is=="string"?A.createElement(p,{is:d.is}):A.createElement(p)}}b[Q2]=n,b[r4]=d;t:for(A=n.child;A!==null;){if(A.tag===5||A.tag===6)b.appendChild(A.stateNode);else if(A.tag!==4&&A.tag!==27&&A.child!==null){A.child.return=A,A=A.child;continue}if(A===n)break t;for(;A.sibling===null;){if(A.return===null||A.return===n)break t;A=A.return}A.sibling.return=A.return,A=A.sibling}n.stateNode=b;t:switch(t4(b,p,d),p){case"button":case"input":case"select":case"textarea":d=!!d.autoFocus;break t;case"img":d=!0;break t;default:d=!1}d&&z3(n)}}return C2(n),Qc(n,n.type,e===null?null:e.memoizedProps,n.pendingProps,i),null;case 6:if(e&&n.stateNode!=null)e.memoizedProps!==d&&z3(n);else{if(typeof d!="string"&&n.stateNode===null)throw Error(l(166));if(e=i1.current,d6(n)){if(e=n.stateNode,i=n.memoizedProps,d=null,p=X2,p!==null)switch(p.tag){case 27:case 5:d=p.memoizedProps}e[Q2]=n,e=!!(e.nodeValue===i||d!==null&&d.suppressHydrationWarning===!0||Gs(e.nodeValue,i)),e||_3(n,!0)}else e=Z8(e).createTextNode(d),e[Q2]=n,n.stateNode=e}return C2(n),null;case 31:if(i=n.memoizedState,e===null||e.memoizedState!==null){if(d=d6(n),i!==null){if(e===null){if(!d)throw Error(l(318));if(e=n.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(l(557));e[Q2]=n}else w0(),(n.flags&128)===0&&(n.memoizedState=null),n.flags|=4;C2(n),e=!1}else i=lc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),e=!0;if(!e)return n.flags&256?(g4(n),n):(g4(n),null);if((n.flags&128)!==0)throw Error(l(558))}return C2(n),null;case 13:if(d=n.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(p=d6(n),d!==null&&d.dehydrated!==null){if(e===null){if(!p)throw Error(l(318));if(p=n.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(l(317));p[Q2]=n}else w0(),(n.flags&128)===0&&(n.memoizedState=null),n.flags|=4;C2(n),p=!1}else p=lc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=p),p=!0;if(!p)return n.flags&256?(g4(n),n):(g4(n),null)}return g4(n),(n.flags&128)!==0?(n.lanes=i,n):(i=d!==null,e=e!==null&&e.memoizedState!==null,i&&(d=n.child,p=null,d.alternate!==null&&d.alternate.memoizedState!==null&&d.alternate.memoizedState.cachePool!==null&&(p=d.alternate.memoizedState.cachePool.pool),b=null,d.memoizedState!==null&&d.memoizedState.cachePool!==null&&(b=d.memoizedState.cachePool.pool),b!==p&&(d.flags|=2048)),i!==e&&i&&(n.child.flags|=8192),V8(n,n.updateQueue),C2(n),null);case 4:return b1(),e===null&&bn(n.stateNode.containerInfo),C2(n),null;case 10:return p3(n.type),C2(n),null;case 19:if(O(F2),d=n.memoizedState,d===null)return C2(n),null;if(p=(n.flags&128)!==0,b=d.rendering,b===null)if(p)_t(d,!1);else{if(k2!==0||e!==null&&(e.flags&128)!==0)for(e=n.child;e!==null;){if(b=g8(e),b!==null){for(n.flags|=128,_t(d,!1),e=b.updateQueue,n.updateQueue=e,V8(n,e),n.subtreeFlags=0,e=i,i=n.child;i!==null;)Sl(i,e),i=i.sibling;return Y(F2,F2.current&1|2),c2&&m3(n,d.treeForkCount),n.child}e=e.sibling}d.tail!==null&&l1()>E8&&(n.flags|=128,p=!0,_t(d,!1),n.lanes=4194304)}else{if(!p)if(e=g8(b),e!==null){if(n.flags|=128,p=!0,e=e.updateQueue,n.updateQueue=e,V8(n,e),_t(d,!0),d.tail===null&&d.tailMode==="hidden"&&!b.alternate&&!c2)return C2(n),null}else 2*l1()-d.renderingStartTime>E8&&i!==536870912&&(n.flags|=128,p=!0,_t(d,!1),n.lanes=4194304);d.isBackwards?(b.sibling=n.child,n.child=b):(e=d.last,e!==null?e.sibling=b:n.child=b,d.last=b)}return d.tail!==null?(e=d.tail,d.rendering=e,d.tail=e.sibling,d.renderingStartTime=l1(),e.sibling=null,i=F2.current,Y(F2,p?i&1|2:i&1),c2&&m3(n,d.treeForkCount),e):(C2(n),null);case 22:case 23:return g4(n),bc(),d=n.memoizedState!==null,e!==null?e.memoizedState!==null!==d&&(n.flags|=8192):d&&(n.flags|=8192),d?(i&536870912)!==0&&(n.flags&128)===0&&(C2(n),n.subtreeFlags&6&&(n.flags|=8192)):C2(n),i=n.updateQueue,i!==null&&V8(n,i.retryQueue),i=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(i=e.memoizedState.cachePool.pool),d=null,n.memoizedState!==null&&n.memoizedState.cachePool!==null&&(d=n.memoizedState.cachePool.pool),d!==i&&(n.flags|=2048),e!==null&&O(S0),null;case 24:return i=null,e!==null&&(i=e.memoizedState.cache),n.memoizedState.cache!==i&&(n.flags|=2048),p3(q2),C2(n),null;case 25:return null;case 30:return null}throw Error(l(156,n.tag))}function gp(e,n){switch(ac(n),n.tag){case 1:return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 3:return p3(q2),b1(),e=n.flags,(e&65536)!==0&&(e&128)===0?(n.flags=e&-65537|128,n):null;case 26:case 27:case 5:return x1(n),null;case 31:if(n.memoizedState!==null){if(g4(n),n.alternate===null)throw Error(l(340));w0()}return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 13:if(g4(n),e=n.memoizedState,e!==null&&e.dehydrated!==null){if(n.alternate===null)throw Error(l(340));w0()}return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 19:return O(F2),null;case 4:return b1(),null;case 10:return p3(n.type),null;case 22:case 23:return g4(n),bc(),e!==null&&O(S0),e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 24:return p3(q2),null;case 25:return null;default:return null}}function Wi(e,n){switch(ac(n),n.tag){case 3:p3(q2),b1();break;case 26:case 27:case 5:x1(n);break;case 4:b1();break;case 31:n.memoizedState!==null&&g4(n);break;case 13:g4(n);break;case 19:O(F2);break;case 10:p3(n.type);break;case 22:case 23:g4(n),bc(),e!==null&&O(S0);break;case 24:p3(q2)}}function Ot(e,n){try{var i=n.updateQueue,d=i!==null?i.lastEffect:null;if(d!==null){var p=d.next;i=p;do{if((i.tag&e)===e){d=void 0;var b=i.create,A=i.inst;d=b(),A.destroy=d}i=i.next}while(i!==p)}}catch(F){u2(n,n.return,F)}}function G3(e,n,i){try{var d=n.updateQueue,p=d!==null?d.lastEffect:null;if(p!==null){var b=p.next;d=b;do{if((d.tag&e)===e){var A=d.inst,F=A.destroy;if(F!==void 0){A.destroy=void 0,p=n;var U=i,c1=F;try{c1()}catch(u1){u2(p,U,u1)}}}d=d.next}while(d!==b)}}catch(u1){u2(n,n.return,u1)}}function Ji(e){var n=e.updateQueue;if(n!==null){var i=e.stateNode;try{Pl(n,i)}catch(d){u2(e,e.return,d)}}}function ts(e,n,i){i.props=N0(e.type,e.memoizedProps),i.state=e.memoizedState;try{i.componentWillUnmount()}catch(d){u2(e,n,d)}}function Ut(e,n){try{var i=e.ref;if(i!==null){switch(e.tag){case 26:case 27:case 5:var d=e.stateNode;break;case 30:d=e.stateNode;break;default:d=e.stateNode}typeof i=="function"?e.refCleanup=i(d):i.current=d}}catch(p){u2(e,n,p)}}function c3(e,n){var i=e.ref,d=e.refCleanup;if(i!==null)if(typeof d=="function")try{d()}catch(p){u2(e,n,p)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof i=="function")try{i(null)}catch(p){u2(e,n,p)}else i.current=null}function es(e){var n=e.type,i=e.memoizedProps,d=e.stateNode;try{t:switch(n){case"button":case"input":case"select":case"textarea":i.autoFocus&&d.focus();break t;case"img":i.src?d.src=i.src:i.srcSet&&(d.srcset=i.srcSet)}}catch(p){u2(e,e.return,p)}}function Xc(e,n,i){try{var d=e.stateNode;_p(d,e.type,i,n),d[r4]=n}catch(p){u2(e,e.return,p)}}function cs(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&t0(e.type)||e.tag===4}function Wc(e){t:for(;;){for(;e.sibling===null;){if(e.return===null||cs(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&t0(e.type)||e.flags&2||e.child===null||e.tag===4)continue t;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Jc(e,n,i){var d=e.tag;if(d===5||d===6)e=e.stateNode,n?(i.nodeType===9?i.body:i.nodeName==="HTML"?i.ownerDocument.body:i).insertBefore(e,n):(n=i.nodeType===9?i.body:i.nodeName==="HTML"?i.ownerDocument.body:i,n.appendChild(e),i=i._reactRootContainer,i!=null||n.onclick!==null||(n.onclick=d3));else if(d!==4&&(d===27&&t0(e.type)&&(i=e.stateNode,n=null),e=e.child,e!==null))for(Jc(e,n,i),e=e.sibling;e!==null;)Jc(e,n,i),e=e.sibling}function B8(e,n,i){var d=e.tag;if(d===5||d===6)e=e.stateNode,n?i.insertBefore(e,n):i.appendChild(e);else if(d!==4&&(d===27&&t0(e.type)&&(i=e.stateNode),e=e.child,e!==null))for(B8(e,n,i),e=e.sibling;e!==null;)B8(e,n,i),e=e.sibling}function ns(e){var n=e.stateNode,i=e.memoizedProps;try{for(var d=e.type,p=n.attributes;p.length;)n.removeAttributeNode(p[0]);t4(n,d,i),n[Q2]=e,n[r4]=i}catch(b){u2(e,e.return,b)}}var y3=!1,U2=!1,tn=!1,as=typeof WeakSet=="function"?WeakSet:Set,Y2=null;function bp(e,n){if(e=e.containerInfo,wn=J8,e=pl(e),G7(e)){if("selectionStart"in e)var i={start:e.selectionStart,end:e.selectionEnd};else t:{i=(i=e.ownerDocument)&&i.defaultView||window;var d=i.getSelection&&i.getSelection();if(d&&d.rangeCount!==0){i=d.anchorNode;var p=d.anchorOffset,b=d.focusNode;d=d.focusOffset;try{i.nodeType,b.nodeType}catch{i=null;break t}var A=0,F=-1,U=-1,c1=0,u1=0,f1=e,r1=null;e:for(;;){for(var s1;f1!==i||p!==0&&f1.nodeType!==3||(F=A+p),f1!==b||d!==0&&f1.nodeType!==3||(U=A+d),f1.nodeType===3&&(A+=f1.nodeValue.length),(s1=f1.firstChild)!==null;)r1=f1,f1=s1;for(;;){if(f1===e)break e;if(r1===i&&++c1===p&&(F=A),r1===b&&++u1===d&&(U=A),(s1=f1.nextSibling)!==null)break;f1=r1,r1=f1.parentNode}f1=s1}i=F===-1||U===-1?null:{start:F,end:U}}else i=null}i=i||{start:0,end:0}}else i=null;for(Mn={focusedElem:e,selectionRange:i},J8=!1,Y2=n;Y2!==null;)if(n=Y2,e=n.child,(n.subtreeFlags&1028)!==0&&e!==null)e.return=n,Y2=e;else for(;Y2!==null;){switch(n=Y2,b=n.alternate,e=n.flags,n.tag){case 0:if((e&4)!==0&&(e=n.updateQueue,e=e!==null?e.events:null,e!==null))for(i=0;i title"))),t4(b,d,i),b[Q2]=e,G2(b),d=b;break t;case"link":var A=uo("link","href",p).get(d+(i.href||""));if(A){for(var F=0;Fv2&&(A=v2,v2=O1,O1=A);var X=ml(F,O1),$=ml(F,v2);if(X&&$&&(s1.rangeCount!==1||s1.anchorNode!==X.node||s1.anchorOffset!==X.offset||s1.focusNode!==$.node||s1.focusOffset!==$.offset)){var e1=f1.createRange();e1.setStart(X.node,X.offset),s1.removeAllRanges(),O1>v2?(s1.addRange(e1),s1.extend($.node,$.offset)):(e1.setEnd($.node,$.offset),s1.addRange(e1))}}}}for(f1=[],s1=F;s1=s1.parentNode;)s1.nodeType===1&&f1.push({element:s1,left:s1.scrollLeft,top:s1.scrollTop});for(typeof F.focus=="function"&&F.focus(),F=0;Fi?32:i,T.T=null,i=sn,sn=null;var b=X3,A=A3;if($2=0,S6=X3=null,A3=0,(i2&6)!==0)throw Error(l(331));var F=i2;if(i2|=4,vs(b.current),hs(b,b.current,A,i),i2=F,Yt(0,!1),H1&&typeof H1.onPostCommitFiberRoot=="function")try{H1.onPostCommitFiberRoot(b2,b)}catch{}return!0}finally{I.p=p,T.T=d,ks(e,n)}}function Rs(e,n,i){n=L4(i,n),n=_c(e.stateNode,n,2),e=I3(e,n,2),e!==null&&(O4(e,2),n3(e))}function u2(e,n,i){if(e.tag===3)Rs(e,e,i);else for(;n!==null;){if(n.tag===3){Rs(n,e,i);break}else if(n.tag===1){var d=n.stateNode;if(typeof n.type.getDerivedStateFromError=="function"||typeof d.componentDidCatch=="function"&&(Q3===null||!Q3.has(d))){e=L4(i,e),i=Ei(2),d=I3(n,i,2),d!==null&&(Ti(i,d,n,e),O4(d,2),n3(d));break}}n=n.return}}function hn(e,n,i){var d=e.pingCache;if(d===null){d=e.pingCache=new wp;var p=new Set;d.set(n,p)}else p=d.get(n),p===void 0&&(p=new Set,d.set(n,p));p.has(i)||(nn=!0,p.add(i),e=Hp.bind(null,e,n,i),n.then(e,e))}function Hp(e,n,i){var d=e.pingCache;d!==null&&d.delete(n),e.pingedLanes|=e.suspendedLanes&i,e.warmLanes&=~i,g2===e&&(t2&i)===i&&(k2===4||k2===3&&(t2&62914560)===t2&&300>l1()-R8?(i2&2)===0&&A6(e,0):an|=i,C6===t2&&(C6=0)),n3(e)}function Es(e,n){n===0&&(n=x2()),e=z0(e,n),e!==null&&(O4(e,n),n3(e))}function Lp(e){var n=e.memoizedState,i=0;n!==null&&(i=n.retryLane),Es(e,i)}function jp(e,n){var i=0;switch(e.tag){case 31:case 13:var d=e.stateNode,p=e.memoizedState;p!==null&&(i=p.retryLane);break;case 19:d=e.stateNode;break;case 22:d=e.stateNode._retryCache;break;default:throw Error(l(314))}d!==null&&d.delete(n),Es(e,i)}function Np(e,n){return S1(e,n)}var U8=null,L6=null,fn=!1,P8=!1,mn=!1,J3=0;function n3(e){e!==L6&&e.next===null&&(L6===null?U8=L6=e:L6=L6.next=e),P8=!0,fn||(fn=!0,Bp())}function Yt(e,n){if(!mn&&P8){mn=!0;do for(var i=!1,d=U8;d!==null;){if(e!==0){var p=d.pendingLanes;if(p===0)var b=0;else{var A=d.suspendedLanes,F=d.pingedLanes;b=(1<<31-V2(42|e)+1)-1,b&=p&~(A&~F),b=b&201326741?b&201326741|1:b?b|2:0}b!==0&&(i=!0,_s(d,b))}else b=t2,b=h2(d,d===g2?b:0,d.cancelPendingCommit!==null||d.timeoutHandle!==-1),(b&3)===0||s2(d,b)||(i=!0,_s(d,b));d=d.next}while(i);mn=!1}}function Vp(){Ts()}function Ts(){P8=fn=!1;var e=0;J3!==0&&Up()&&(e=J3);for(var n=l1(),i=null,d=U8;d!==null;){var p=d.next,b=Ds(d,n);b===0?(d.next=null,i===null?U8=p:i.next=p,p===null&&(L6=i)):(i=d,(e!==0||(b&3)!==0)&&(P8=!0)),d=p}$2!==0&&$2!==5||Yt(e),J3!==0&&(J3=0)}function Ds(e,n){for(var i=e.suspendedLanes,d=e.pingedLanes,p=e.expirationTimes,b=e.pendingLanes&-62914561;0F)break;var u1=U.transferSize,f1=U.initiatorType;u1&&Ys(f1)&&(U=U.responseEnd,A+=u1*(U"u"?null:document;function lo(e,n,i){var d=j6;if(d&&typeof n=="string"&&n){var p=A4(n);p='link[rel="'+e+'"][href="'+p+'"]',typeof i=="string"&&(p+='[crossorigin="'+i+'"]'),ro.has(p)||(ro.add(p),e={rel:e,crossOrigin:i,href:n},d.querySelector(p)===null&&(n=d.createElement("link"),t4(n,"link",e),G2(n),d.head.appendChild(n)))}}function Xp(e){H3.D(e),lo("dns-prefetch",e,null)}function Wp(e,n){H3.C(e,n),lo("preconnect",e,n)}function Jp(e,n,i){H3.L(e,n,i);var d=j6;if(d&&e&&n){var p='link[rel="preload"][as="'+A4(n)+'"]';n==="image"&&i&&i.imageSrcSet?(p+='[imagesrcset="'+A4(i.imageSrcSet)+'"]',typeof i.imageSizes=="string"&&(p+='[imagesizes="'+A4(i.imageSizes)+'"]')):p+='[href="'+A4(e)+'"]';var b=p;switch(n){case"style":b=N6(e);break;case"script":b=V6(e)}F4.has(b)||(e=z({rel:"preload",href:n==="image"&&i&&i.imageSrcSet?void 0:e,as:n},i),F4.set(b,e),d.querySelector(p)!==null||n==="style"&&d.querySelector(Wt(b))||n==="script"&&d.querySelector(Jt(b))||(n=d.createElement("link"),t4(n,"link",e),G2(n),d.head.appendChild(n)))}}function tx(e,n){H3.m(e,n);var i=j6;if(i&&e){var d=n&&typeof n.as=="string"?n.as:"script",p='link[rel="modulepreload"][as="'+A4(d)+'"][href="'+A4(e)+'"]',b=p;switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":b=V6(e)}if(!F4.has(b)&&(e=z({rel:"modulepreload",href:e},n),F4.set(b,e),i.querySelector(p)===null)){switch(d){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(i.querySelector(Jt(b)))return}d=i.createElement("link"),t4(d,"link",e),G2(d),i.head.appendChild(d)}}}function ex(e,n,i){H3.S(e,n,i);var d=j6;if(d&&e){var p=W0(d).hoistableStyles,b=N6(e);n=n||"default";var A=p.get(b);if(!A){var F={loading:0,preload:null};if(A=d.querySelector(Wt(b)))F.loading=5;else{e=z({rel:"stylesheet",href:e,"data-precedence":n},i),(i=F4.get(b))&&Nn(e,i);var U=A=d.createElement("link");G2(U),t4(U,"link",e),U._p=new Promise(function(c1,u1){U.onload=c1,U.onerror=u1}),U.addEventListener("load",function(){F.loading|=1}),U.addEventListener("error",function(){F.loading|=2}),F.loading|=4,Y8(A,n,d)}A={type:"stylesheet",instance:A,count:1,state:F},p.set(b,A)}}}function cx(e,n){H3.X(e,n);var i=j6;if(i&&e){var d=W0(i).hoistableScripts,p=V6(e),b=d.get(p);b||(b=i.querySelector(Jt(p)),b||(e=z({src:e,async:!0},n),(n=F4.get(p))&&Vn(e,n),b=i.createElement("script"),G2(b),t4(b,"link",e),i.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(p,b))}}function nx(e,n){H3.M(e,n);var i=j6;if(i&&e){var d=W0(i).hoistableScripts,p=V6(e),b=d.get(p);b||(b=i.querySelector(Jt(p)),b||(e=z({src:e,async:!0,type:"module"},n),(n=F4.get(p))&&Vn(e,n),b=i.createElement("script"),G2(b),t4(b,"link",e),i.head.appendChild(b)),b={type:"script",instance:b,count:1,state:null},d.set(p,b))}}function io(e,n,i,d){var p=(p=i1.current)?G8(p):null;if(!p)throw Error(l(446));switch(e){case"meta":case"title":return null;case"style":return typeof i.precedence=="string"&&typeof i.href=="string"?(n=N6(i.href),i=W0(p).hoistableStyles,d=i.get(n),d||(d={type:"style",instance:null,count:0,state:null},i.set(n,d)),d):{type:"void",instance:null,count:0,state:null};case"link":if(i.rel==="stylesheet"&&typeof i.href=="string"&&typeof i.precedence=="string"){e=N6(i.href);var b=W0(p).hoistableStyles,A=b.get(e);if(A||(p=p.ownerDocument||p,A={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},b.set(e,A),(b=p.querySelector(Wt(e)))&&!b._p&&(A.instance=b,A.state.loading=5),F4.has(e)||(i={rel:"preload",as:"style",href:i.href,crossOrigin:i.crossOrigin,integrity:i.integrity,media:i.media,hrefLang:i.hrefLang,referrerPolicy:i.referrerPolicy},F4.set(e,i),b||ax(p,e,i,A.state))),n&&d===null)throw Error(l(528,""));return A}if(n&&d!==null)throw Error(l(529,""));return null;case"script":return n=i.async,i=i.src,typeof i=="string"&&n&&typeof n!="function"&&typeof n!="symbol"?(n=V6(i),i=W0(p).hoistableScripts,d=i.get(n),d||(d={type:"script",instance:null,count:0,state:null},i.set(n,d)),d):{type:"void",instance:null,count:0,state:null};default:throw Error(l(444,e))}}function N6(e){return'href="'+A4(e)+'"'}function Wt(e){return'link[rel="stylesheet"]['+e+"]"}function so(e){return z({},e,{"data-precedence":e.precedence,precedence:null})}function ax(e,n,i,d){e.querySelector('link[rel="preload"][as="style"]['+n+"]")?d.loading=1:(n=e.createElement("link"),d.preload=n,n.addEventListener("load",function(){return d.loading|=1}),n.addEventListener("error",function(){return d.loading|=2}),t4(n,"link",i),G2(n),e.head.appendChild(n))}function V6(e){return'[src="'+A4(e)+'"]'}function Jt(e){return"script[async]"+e}function oo(e,n,i){if(n.count++,n.instance===null)switch(n.type){case"style":var d=e.querySelector('style[data-href~="'+A4(i.href)+'"]');if(d)return n.instance=d,G2(d),d;var p=z({},i,{"data-href":i.href,"data-precedence":i.precedence,href:null,precedence:null});return d=(e.ownerDocument||e).createElement("style"),G2(d),t4(d,"style",p),Y8(d,i.precedence,e),n.instance=d;case"stylesheet":p=N6(i.href);var b=e.querySelector(Wt(p));if(b)return n.state.loading|=4,n.instance=b,G2(b),b;d=so(i),(p=F4.get(p))&&Nn(d,p),b=(e.ownerDocument||e).createElement("link"),G2(b);var A=b;return A._p=new Promise(function(F,U){A.onload=F,A.onerror=U}),t4(b,"link",d),n.state.loading|=4,Y8(b,i.precedence,e),n.instance=b;case"script":return b=V6(i.src),(p=e.querySelector(Jt(b)))?(n.instance=p,G2(p),p):(d=i,(p=F4.get(b))&&(d=z({},i),Vn(d,p)),e=e.ownerDocument||e,p=e.createElement("script"),G2(p),t4(p,"link",d),e.head.appendChild(p),n.instance=p);case"void":return null;default:throw Error(l(443,n.type))}else n.type==="stylesheet"&&(n.state.loading&4)===0&&(d=n.instance,n.state.loading|=4,Y8(d,i.precedence,e));return n.instance}function Y8(e,n,i){for(var d=i.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),p=d.length?d[d.length-1]:null,b=p,A=0;A title"):null)}function rx(e,n,i){if(i===1||n.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof n.precedence!="string"||typeof n.href!="string"||n.href==="")break;return!0;case"link":if(typeof n.rel!="string"||typeof n.href!="string"||n.href===""||n.onLoad||n.onError)break;switch(n.rel){case"stylesheet":return e=n.disabled,typeof n.precedence=="string"&&e==null;default:return!0}case"script":if(n.async&&typeof n.async!="function"&&typeof n.async!="symbol"&&!n.onLoad&&!n.onError&&n.src&&typeof n.src=="string")return!0}return!1}function fo(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function lx(e,n,i,d){if(i.type==="stylesheet"&&(typeof d.media!="string"||matchMedia(d.media).matches!==!1)&&(i.state.loading&4)===0){if(i.instance===null){var p=N6(d.href),b=n.querySelector(Wt(p));if(b){n=b._p,n!==null&&typeof n=="object"&&typeof n.then=="function"&&(e.count++,e=Q8.bind(e),n.then(e,e)),i.state.loading|=4,i.instance=b,G2(b);return}b=n.ownerDocument||n,d=so(d),(p=F4.get(p))&&Nn(d,p),b=b.createElement("link"),G2(b);var A=b;A._p=new Promise(function(F,U){A.onload=F,A.onerror=U}),t4(b,"link",d),i.instance=b}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(i,n),(n=i.state.preload)&&(i.state.loading&3)===0&&(e.count++,i=Q8.bind(e),n.addEventListener("load",i),n.addEventListener("error",i))}}var Bn=0;function ix(e,n){return e.stylesheets&&e.count===0&&W8(e,e.stylesheets),0Bn?50:800)+n);return e.unsuspend=i,function(){e.unsuspend=null,clearTimeout(d),clearTimeout(p)}}:null}function Q8(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)W8(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var X8=null;function W8(e,n){e.stylesheets=null,e.unsuspend!==null&&(e.count++,X8=new Map,n.forEach(sx,e),X8=null,Q8.call(e))}function sx(e,n){if(!(n.state.loading&4)){var i=X8.get(e);if(i)var d=i.get(null);else{i=new Map,X8.set(e,i);for(var p=e.querySelectorAll("link[data-precedence],style[data-precedence]"),b=0;b"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(c){console.error(c)}}return t(),On.exports=Ax(),On.exports}var Lx=Hx();var Do="popstate";function jx(t={}){function c(l,s){let{pathname:o,search:h,hash:m}=l.location;return E9("",{pathname:o,search:h,hash:m},s.state&&s.state.usr||null,s.state&&s.state.key||"default")}function r(l,s){return typeof s=="string"?s:Ae(s)}return Vx(c,r,null,t)}function L2(t,c){if(t===!1||t===null||typeof t>"u")throw new Error(c)}function Q4(t,c){if(!t){typeof console<"u"&&console.warn(c);try{throw new Error(c)}catch{}}}function Nx(){return Math.random().toString(36).substring(2,10)}function qo(t,c){return{usr:t.state,key:t.key,idx:c}}function E9(t,c,r=null,l){return{pathname:typeof t=="string"?t:t.pathname,search:"",hash:"",...typeof c=="string"?ct(c):c,state:r,key:c&&c.key||l||Nx()}}function Ae({pathname:t="/",search:c="",hash:r=""}){return c&&c!=="?"&&(t+=c.charAt(0)==="?"?c:"?"+c),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function ct(t){let c={};if(t){let r=t.indexOf("#");r>=0&&(c.hash=t.substring(r),t=t.substring(0,r));let l=t.indexOf("?");l>=0&&(c.search=t.substring(l),t=t.substring(0,l)),t&&(c.pathname=t)}return c}function Vx(t,c,r,l={}){let{window:s=document.defaultView,v5Compat:o=!1}=l,h=s.history,m="POP",v=null,g=x();g==null&&(g=0,h.replaceState({...h.state,idx:g},""));function x(){return(h.state||{idx:null}).idx}function z(){m="POP";let M=x(),V=M==null?null:M-g;g=M,v&&v({action:m,location:H.location,delta:V})}function y(M,V){m="PUSH";let C=E9(H.location,M,V);g=x()+1;let S=qo(C,g),j=H.createHref(C);try{h.pushState(S,"",j)}catch(E){if(E instanceof DOMException&&E.name==="DataCloneError")throw E;s.location.assign(j)}o&&v&&v({action:m,location:H.location,delta:1})}function w(M,V){m="REPLACE";let C=E9(H.location,M,V);g=x();let S=qo(C,g),j=H.createHref(C);h.replaceState(S,"",j),o&&v&&v({action:m,location:H.location,delta:0})}function L(M){return Bx(M)}let H={get action(){return m},get location(){return t(s,h)},listen(M){if(v)throw new Error("A history only accepts one active listener");return s.addEventListener(Do,z),v=M,()=>{s.removeEventListener(Do,z),v=null}},createHref(M){return c(s,M)},createURL:L,encodeLocation(M){let V=L(M);return{pathname:V.pathname,search:V.search,hash:V.hash}},push:y,replace:w,go(M){return h.go(M)}};return H}function Bx(t,c=!1){let r="http://localhost";typeof window<"u"&&(r=window.location.origin!=="null"?window.location.origin:window.location.href),L2(r,"No window.location.(origin|href) available to create URL");let l=typeof t=="string"?t:Ae(t);return l=l.replace(/ $/,"%20"),!c&&l.startsWith("//")&&(l=r+l),new URL(l,r)}function Kd(t,c,r="/"){return kx(t,c,r,!1)}function kx(t,c,r,l){let s=typeof c=="string"?ct(c):c,o=B3(s.pathname||"/",r);if(o==null)return null;let h=Qd(t);Fx(h);let m=null;for(let v=0;m==null&&v{let x={relativePath:g===void 0?h.path||"":g,caseSensitive:h.caseSensitive===!0,childrenIndex:m,route:h};if(x.relativePath.startsWith("/")){if(!x.relativePath.startsWith(l)&&v)return;L2(x.relativePath.startsWith(l),`Absolute route path "${x.relativePath}" nested under path "${l}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),x.relativePath=x.relativePath.slice(l.length)}let z=V3([l,x.relativePath]),y=r.concat(x);h.children&&h.children.length>0&&(L2(h.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${z}".`),Qd(h.children,c,y,z,v)),!(h.path==null&&!h.index)&&c.push({path:z,score:Ox(z,h.index),routesMeta:y})};return t.forEach((h,m)=>{if(h.path===""||!h.path?.includes("?"))o(h,m);else for(let v of Xd(h.path))o(h,m,!0,v)}),c}function Xd(t){let c=t.split("/");if(c.length===0)return[];let[r,...l]=c,s=r.endsWith("?"),o=r.replace(/\?$/,"");if(l.length===0)return s?[o,""]:[o];let h=Xd(l.join("/")),m=[];return m.push(...h.map(v=>v===""?o:[o,v].join("/"))),s&&m.push(...h),m.map(v=>t.startsWith("/")&&v===""?"/":v)}function Fx(t){t.sort((c,r)=>c.score!==r.score?r.score-c.score:Ux(c.routesMeta.map(l=>l.childrenIndex),r.routesMeta.map(l=>l.childrenIndex)))}var Rx=/^:[\w-]+$/,Ex=3,Tx=2,Dx=1,qx=10,_x=-2,_o=t=>t==="*";function Ox(t,c){let r=t.split("/"),l=r.length;return r.some(_o)&&(l+=_x),c&&(l+=Tx),r.filter(s=>!_o(s)).reduce((s,o)=>s+(Rx.test(o)?Ex:o===""?Dx:qx),l)}function Ux(t,c){return t.length===c.length&&t.slice(0,-1).every((l,s)=>l===c[s])?t[t.length-1]-c[c.length-1]:0}function Px(t,c,r=!1){let{routesMeta:l}=t,s={},o="/",h=[];for(let m=0;m{if(x==="*"){let L=m[y]||"";h=o.slice(0,o.length-L.length).replace(/(.)\/+$/,"$1")}const w=m[y];return z&&!w?g[x]=void 0:g[x]=(w||"").replace(/%2F/g,"/"),g},{}),pathname:o,pathnameBase:h,pattern:t}}function Ix(t,c=!1,r=!0){Q4(t==="*"||!t.endsWith("*")||t.endsWith("/*"),`Route path "${t}" will be treated as if it were "${t.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${t.replace(/\*$/,"/*")}".`);let l=[],s="^"+t.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(h,m,v)=>(l.push({paramName:m,isOptional:v!=null}),v?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return t.endsWith("*")?(l.push({paramName:"*"}),s+=t==="*"||t==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?s+="\\/*$":t!==""&&t!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,c?void 0:"i"),l]}function $x(t){try{return t.split("/").map(c=>decodeURIComponent(c).replace(/\//g,"%2F")).join("/")}catch(c){return Q4(!1,`The URL path "${t}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${c}).`),t}}function B3(t,c){if(c==="/")return t;if(!t.toLowerCase().startsWith(c.toLowerCase()))return null;let r=c.endsWith("/")?c.length-1:c.length,l=t.charAt(r);return l&&l!=="/"?null:t.slice(r)||"/"}var Zx=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Gx=t=>Zx.test(t);function Yx(t,c="/"){let{pathname:r,search:l="",hash:s=""}=typeof t=="string"?ct(t):t,o;if(r)if(Gx(r))o=r;else{if(r.includes("//")){let h=r;r=r.replace(/\/\/+/g,"/"),Q4(!1,`Pathnames cannot have embedded double slashes - normalizing ${h} -> ${r}`)}r.startsWith("/")?o=Oo(r.substring(1),"/"):o=Oo(r,c)}else o=c;return{pathname:o,search:Xx(l),hash:Wx(s)}}function Oo(t,c){let r=c.replace(/\/+$/,"").split("/");return t.split("/").forEach(s=>{s===".."?r.length>1&&r.pop():s!=="."&&r.push(s)}),r.length>1?r.join("/"):"/"}function $n(t,c,r,l){return`Cannot include a '${t}' character in a manually specified \`to.${c}\` field [${JSON.stringify(l)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Kx(t){return t.filter((c,r)=>r===0||c.route.path&&c.route.path.length>0)}function Wd(t){let c=Kx(t);return c.map((r,l)=>l===c.length-1?r.pathname:r.pathnameBase)}function Jd(t,c,r,l=!1){let s;typeof t=="string"?s=ct(t):(s={...t},L2(!s.pathname||!s.pathname.includes("?"),$n("?","pathname","search",s)),L2(!s.pathname||!s.pathname.includes("#"),$n("#","pathname","hash",s)),L2(!s.search||!s.search.includes("#"),$n("#","search","hash",s)));let o=t===""||s.pathname==="",h=o?"/":s.pathname,m;if(h==null)m=r;else{let z=c.length-1;if(!l&&h.startsWith("..")){let y=h.split("/");for(;y[0]==="..";)y.shift(),z-=1;s.pathname=y.join("/")}m=z>=0?c[z]:"/"}let v=Yx(s,m),g=h&&h!=="/"&&h.endsWith("/"),x=(o||h===".")&&r.endsWith("/");return!v.pathname.endsWith("/")&&(g||x)&&(v.pathname+="/"),v}var V3=t=>t.join("/").replace(/\/\/+/g,"/"),Qx=t=>t.replace(/\/+$/,"").replace(/^\/*/,"/"),Xx=t=>!t||t==="?"?"":t.startsWith("?")?t:"?"+t,Wx=t=>!t||t==="#"?"":t.startsWith("#")?t:"#"+t;function Jx(t){return t!=null&&typeof t.status=="number"&&typeof t.statusText=="string"&&typeof t.internal=="boolean"&&"data"in t}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var th=["POST","PUT","PATCH","DELETE"];new Set(th);var tg=["GET",...th];new Set(tg);var nt=f.createContext(null);nt.displayName="DataRouter";var s7=f.createContext(null);s7.displayName="DataRouterState";f.createContext(!1);var eh=f.createContext({isTransitioning:!1});eh.displayName="ViewTransition";var eg=f.createContext(new Map);eg.displayName="Fetchers";var cg=f.createContext(null);cg.displayName="Await";var s3=f.createContext(null);s3.displayName="Navigation";var qe=f.createContext(null);qe.displayName="Location";var k3=f.createContext({outlet:null,matches:[],isDataRoute:!1});k3.displayName="Route";var Ha=f.createContext(null);Ha.displayName="RouteError";function ng(t,{relative:c}={}){L2(_e(),"useHref() may be used only in the context of a component.");let{basename:r,navigator:l}=f.useContext(s3),{hash:s,pathname:o,search:h}=Oe(t,{relative:c}),m=o;return r!=="/"&&(m=o==="/"?r:V3([r,o])),l.createHref({pathname:m,search:h,hash:s})}function _e(){return f.useContext(qe)!=null}function P0(){return L2(_e(),"useLocation() may be used only in the context of a component."),f.useContext(qe).location}var ch="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function nh(t){f.useContext(s3).static||f.useLayoutEffect(t)}function La(){let{isDataRoute:t}=f.useContext(k3);return t?pg():ag()}function ag(){L2(_e(),"useNavigate() may be used only in the context of a component.");let t=f.useContext(nt),{basename:c,navigator:r}=f.useContext(s3),{matches:l}=f.useContext(k3),{pathname:s}=P0(),o=JSON.stringify(Wd(l)),h=f.useRef(!1);return nh(()=>{h.current=!0}),f.useCallback((v,g={})=>{if(Q4(h.current,ch),!h.current)return;if(typeof v=="number"){r.go(v);return}let x=Jd(v,JSON.parse(o),s,g.relative==="path");t==null&&c!=="/"&&(x.pathname=x.pathname==="/"?c:V3([c,x.pathname])),(g.replace?r.replace:r.push)(x,g.state,g)},[c,r,o,s,t])}f.createContext(null);function Oe(t,{relative:c}={}){let{matches:r}=f.useContext(k3),{pathname:l}=P0(),s=JSON.stringify(Wd(r));return f.useMemo(()=>Jd(t,JSON.parse(s),l,c==="path"),[t,s,l,c])}function rg(t,c){return ah(t,c)}function ah(t,c,r,l,s){L2(_e(),"useRoutes() may be used only in the context of a component.");let{navigator:o}=f.useContext(s3),{matches:h}=f.useContext(k3),m=h[h.length-1],v=m?m.params:{},g=m?m.pathname:"/",x=m?m.pathnameBase:"/",z=m&&m.route;{let C=z&&z.path||"";rh(g,!z||C.endsWith("*")||C.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${g}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. - -Please change the parent to .`)}let y=P0(),w;if(c){let C=typeof c=="string"?ct(c):c;L2(x==="/"||C.pathname?.startsWith(x),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${x}" but pathname "${C.pathname}" was given in the \`location\` prop.`),w=C}else w=y;let L=w.pathname||"/",H=L;if(x!=="/"){let C=x.replace(/^\//,"").split("/");H="/"+L.replace(/^\//,"").split("/").slice(C.length).join("/")}let M=Kd(t,{pathname:H});Q4(z||M!=null,`No routes matched location "${w.pathname}${w.search}${w.hash}" `),Q4(M==null||M[M.length-1].route.element!==void 0||M[M.length-1].route.Component!==void 0||M[M.length-1].route.lazy!==void 0,`Matched leaf route at location "${w.pathname}${w.search}${w.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let V=ug(M&&M.map(C=>Object.assign({},C,{params:Object.assign({},v,C.params),pathname:V3([x,o.encodeLocation?o.encodeLocation(C.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:C.pathname]),pathnameBase:C.pathnameBase==="/"?x:V3([x,o.encodeLocation?o.encodeLocation(C.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:C.pathnameBase])})),h,r,l,s);return c&&V?f.createElement(qe.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...w},navigationType:"POP"}},V):V}function lg(){let t=vg(),c=Jx(t)?`${t.status} ${t.statusText}`:t instanceof Error?t.message:JSON.stringify(t),r=t instanceof Error?t.stack:null,l="rgba(200,200,200, 0.5)",s={padding:"0.5rem",backgroundColor:l},o={padding:"2px 4px",backgroundColor:l},h=null;return console.error("Error handled by React Router default ErrorBoundary:",t),h=f.createElement(f.Fragment,null,f.createElement("p",null,"💿 Hey developer 👋"),f.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",f.createElement("code",{style:o},"ErrorBoundary")," or"," ",f.createElement("code",{style:o},"errorElement")," prop on your route.")),f.createElement(f.Fragment,null,f.createElement("h2",null,"Unexpected Application Error!"),f.createElement("h3",{style:{fontStyle:"italic"}},c),r?f.createElement("pre",{style:s},r):null,h)}var ig=f.createElement(lg,null),sg=class extends f.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,c){return c.location!==t.location||c.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:c.error,location:c.location,revalidation:t.revalidation||c.revalidation}}componentDidCatch(t,c){this.props.onError?this.props.onError(t,c):console.error("React Router caught the following error during render",t)}render(){return this.state.error!==void 0?f.createElement(k3.Provider,{value:this.props.routeContext},f.createElement(Ha.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function og({routeContext:t,match:c,children:r}){let l=f.useContext(nt);return l&&l.static&&l.staticContext&&(c.route.errorElement||c.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=c.route.id),f.createElement(k3.Provider,{value:t},r)}function ug(t,c=[],r=null,l=null,s=null){if(t==null){if(!r)return null;if(r.errors)t=r.matches;else if(c.length===0&&!r.initialized&&r.matches.length>0)t=r.matches;else return null}let o=t,h=r?.errors;if(h!=null){let x=o.findIndex(z=>z.route.id&&h?.[z.route.id]!==void 0);L2(x>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),o=o.slice(0,Math.min(o.length,x+1))}let m=!1,v=-1;if(r)for(let x=0;x=0?o=o.slice(0,v+1):o=[o[0]];break}}}let g=r&&l?(x,z)=>{l(x,{location:r.location,params:r.matches?.[0]?.params??{},errorInfo:z})}:void 0;return o.reduceRight((x,z,y)=>{let w,L=!1,H=null,M=null;r&&(w=h&&z.route.id?h[z.route.id]:void 0,H=z.route.errorElement||ig,m&&(v<0&&y===0?(rh("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),L=!0,M=null):v===y&&(L=!0,M=z.route.hydrateFallbackElement||null)));let V=c.concat(o.slice(0,y+1)),C=()=>{let S;return w?S=H:L?S=M:z.route.Component?S=f.createElement(z.route.Component,null):z.route.element?S=z.route.element:S=x,f.createElement(og,{match:z,routeContext:{outlet:x,matches:V,isDataRoute:r!=null},children:S})};return r&&(z.route.ErrorBoundary||z.route.errorElement||y===0)?f.createElement(sg,{location:r.location,revalidation:r.revalidation,component:H,error:w,children:C(),routeContext:{outlet:null,matches:V,isDataRoute:!0},onError:g}):C()},null)}function ja(t){return`${t} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function dg(t){let c=f.useContext(nt);return L2(c,ja(t)),c}function hg(t){let c=f.useContext(s7);return L2(c,ja(t)),c}function fg(t){let c=f.useContext(k3);return L2(c,ja(t)),c}function Na(t){let c=fg(t),r=c.matches[c.matches.length-1];return L2(r.route.id,`${t} can only be used on routes that contain a unique "id"`),r.route.id}function mg(){return Na("useRouteId")}function vg(){let t=f.useContext(Ha),c=hg("useRouteError"),r=Na("useRouteError");return t!==void 0?t:c.errors?.[r]}function pg(){let{router:t}=dg("useNavigate"),c=Na("useNavigate"),r=f.useRef(!1);return nh(()=>{r.current=!0}),f.useCallback(async(s,o={})=>{Q4(r.current,ch),r.current&&(typeof s=="number"?t.navigate(s):await t.navigate(s,{fromRouteId:c,...o}))},[t,c])}var Uo={};function rh(t,c,r){!c&&!Uo[t]&&(Uo[t]=!0,Q4(!1,r))}f.memo(xg);function xg({routes:t,future:c,state:r,unstable_onError:l}){return ah(t,void 0,r,l,c)}function a3(t){L2(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function gg({basename:t="/",children:c=null,location:r,navigationType:l="POP",navigator:s,static:o=!1}){L2(!_e(),"You cannot render a inside another . You should never have more than one in your app.");let h=t.replace(/^\/*/,"/"),m=f.useMemo(()=>({basename:h,navigator:s,static:o,future:{}}),[h,s,o]);typeof r=="string"&&(r=ct(r));let{pathname:v="/",search:g="",hash:x="",state:z=null,key:y="default"}=r,w=f.useMemo(()=>{let L=B3(v,h);return L==null?null:{location:{pathname:L,search:g,hash:x,state:z,key:y},navigationType:l}},[h,v,g,x,z,y,l]);return Q4(w!=null,` is not able to match the URL "${v}${g}${x}" because it does not start with the basename, so the won't render anything.`),w==null?null:f.createElement(s3.Provider,{value:m},f.createElement(qe.Provider,{children:c,value:w}))}function bg({children:t,location:c}){return rg(T9(t),c)}function T9(t,c=[]){let r=[];return f.Children.forEach(t,(l,s)=>{if(!f.isValidElement(l))return;let o=[...c,s];if(l.type===f.Fragment){r.push.apply(r,T9(l.props.children,o));return}L2(l.type===a3,`[${typeof l.type=="string"?l.type:l.type.name}] is not a component. All component children of must be a or `),L2(!l.props.index||!l.props.children,"An index route cannot have child routes.");let h={id:l.props.id||o.join("-"),caseSensitive:l.props.caseSensitive,element:l.props.element,Component:l.props.Component,index:l.props.index,path:l.props.path,middleware:l.props.middleware,loader:l.props.loader,action:l.props.action,hydrateFallbackElement:l.props.hydrateFallbackElement,HydrateFallback:l.props.HydrateFallback,errorElement:l.props.errorElement,ErrorBoundary:l.props.ErrorBoundary,hasErrorBoundary:l.props.hasErrorBoundary===!0||l.props.ErrorBoundary!=null||l.props.errorElement!=null,shouldRevalidate:l.props.shouldRevalidate,handle:l.props.handle,lazy:l.props.lazy};l.props.children&&(h.children=T9(l.props.children,o)),r.push(h)}),r}var B5="get",k5="application/x-www-form-urlencoded";function o7(t){return t!=null&&typeof t.tagName=="string"}function zg(t){return o7(t)&&t.tagName.toLowerCase()==="button"}function yg(t){return o7(t)&&t.tagName.toLowerCase()==="form"}function wg(t){return o7(t)&&t.tagName.toLowerCase()==="input"}function Mg(t){return!!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)}function Cg(t,c){return t.button===0&&(!c||c==="_self")&&!Mg(t)}var l5=null;function Sg(){if(l5===null)try{new FormData(document.createElement("form"),0),l5=!1}catch{l5=!0}return l5}var Ag=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Zn(t){return t!=null&&!Ag.has(t)?(Q4(!1,`"${t}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${k5}"`),null):t}function Hg(t,c){let r,l,s,o,h;if(yg(t)){let m=t.getAttribute("action");l=m?B3(m,c):null,r=t.getAttribute("method")||B5,s=Zn(t.getAttribute("enctype"))||k5,o=new FormData(t)}else if(zg(t)||wg(t)&&(t.type==="submit"||t.type==="image")){let m=t.form;if(m==null)throw new Error('Cannot submit a + ); +} diff --git a/webui/src/components/RoverAccessoryControls/AccessoryControlField.jsx b/webui/src/components/RoverAccessoryControls/AccessoryControlField.jsx new file mode 100644 index 00000000..0864d5fb --- /dev/null +++ b/webui/src/components/RoverAccessoryControls/AccessoryControlField.jsx @@ -0,0 +1,337 @@ +// Accessory Control Field +// Purpose: Maps one firmware-advertised generic control to a compact rover-control surface. +// Scope: Owns browser-local input semantics; transport and device-specific behavior stay outside this file. +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { triggerTouchHaptic } from '../../lib/touchHaptics.js'; + +function integerBound(value, fallback) { + return Number.isInteger(value) ? value : fallback; +} + +function clampInteger(value, minimum, maximum) { + const numeric = Number(value); + if (!Number.isFinite(numeric)) return minimum; + return Math.min(maximum, Math.max(minimum, Math.round(numeric))); +} + +function trimUnicode(value, maximumLength) { + // Array.from counts Unicode code points instead of UTF-16 code units. That + // mirrors Go's rune-count validation for emoji and other non-BMP characters. + return Array.from(String(value ?? '')).slice(0, maximumLength).join(''); +} + +const CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-2 py-1 text-slate-50'; +// Sliders already contain a label row and a separate track. Giving that +// two-row control the same vertical padding as a one-row button made it much +// taller than its neighbors without improving its touch target. +const SLIDER_CARD_CLASS = 'mobile-touch-control rounded-xl border-2 px-1.5 py-0.5 text-slate-50'; +const DISABLED_CLASS = 'disabled:cursor-not-allowed disabled:opacity-40'; + +function SliderControl({ peripheralId, control, disabled, send, value: storedValue }) { + const minimum = integerBound(control.min, 0); + const maximum = integerBound(control.max, minimum); + const value = Number.isInteger(storedValue) + ? clampInteger(storedValue, minimum, maximum) + : minimum; + const trackRef = useRef(null); + const pointerIdRef = useRef(null); + const lastHapticValueRef = useRef(value); + + const valuePercent = useMemo(() => { + if (maximum === minimum) return 50; + return ((value - minimum) / (maximum - minimum)) * 100; + }, [maximum, minimum, value]); + + const sendValue = useCallback((nextValue) => { + const next = clampInteger(nextValue, minimum, maximum); + const hapticStep = Math.max(1, Math.round((maximum - minimum) / 20)); + if (Math.abs(next - lastHapticValueRef.current) >= hapticStep) { + triggerTouchHaptic('camera'); + lastHapticValueRef.current = next; + } + send(peripheralId, control.id, next); + }, [control.id, maximum, minimum, peripheralId, send]); + + const valueFromPointer = useCallback((event) => { + const track = trackRef.current; + if (!track) return value; + const bounds = track.getBoundingClientRect(); + const rawPercent = (event.clientX - bounds.left) / Math.max(1, bounds.width); + return minimum + Math.max(0, Math.min(1, rawPercent)) * (maximum - minimum); + }, [maximum, minimum, value]); + + const updateFromPointer = useCallback((event) => { + sendValue(valueFromPointer(event)); + }, [sendValue, valueFromPointer]); + + const handlePointerDown = useCallback((event) => { + if (disabled || pointerIdRef.current !== null) return; + // This follows VerticalCameraTilt's custom pointer-capture path so a range + // drag remains reliable while another finger is operating the drive pad. + event.preventDefault(); + pointerIdRef.current = event.pointerId; + lastHapticValueRef.current = value; + trackRef.current?.setPointerCapture?.(event.pointerId); + updateFromPointer(event); + }, [disabled, updateFromPointer, value]); + + const handlePointerMove = useCallback((event) => { + if (pointerIdRef.current !== event.pointerId) return; + event.preventDefault(); + updateFromPointer(event); + }, [updateFromPointer]); + + const handlePointerEnd = useCallback((event) => { + if (pointerIdRef.current !== event.pointerId) return; + event.preventDefault(); + pointerIdRef.current = null; + trackRef.current?.releasePointerCapture?.(event.pointerId); + }, []); + + const handleKeyDown = (event) => { + if (disabled) return; + let next = null; + if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = value - 1; + if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = value + 1; + if (event.key === 'Home') next = minimum; + if (event.key === 'End') next = maximum; + if (next == null) return; + event.preventDefault(); + sendValue(next); + }; + + return ( +
+
+ {control.name} + {value} +
+
{ + if (pointerIdRef.current === event.pointerId) pointerIdRef.current = null; + }} + onKeyDown={handleKeyDown} + onContextMenu={(event) => event.preventDefault()} + style={{ touchAction: 'none' }} + className="mobile-touch-control mobile-drag-control relative mt-0.5 h-6 w-full rounded-full border border-emerald-100/80 bg-emerald-950 shadow-inner focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-200" + > + {/* An inset track gives the thumb room to remain entirely inside the + card at both endpoints without browser-specific range styling. */} +
+
+
+
+
+
+ ); +} + +function ToggleControl({ peripheralId, control, disabled, send, value }) { + const enabled = value === true; + + const toggle = () => { + if (disabled) return; + send(peripheralId, control.id, !enabled); + triggerTouchHaptic('button'); + }; + + return ( + + ); +} + +function MomentaryControl({ peripheralId, control, disabled, send, value }) { + const pressed = value === true; + const pressedRef = useRef(false); + const pointerIdRef = useRef(null); + + const release = useCallback(() => { + if (!pressedRef.current) return; + pressedRef.current = false; + pointerIdRef.current = null; + // Always pair a successful press with false, including cancellation, + // permission loss, and replacement of the Accessories view. + send(peripheralId, control.id, false); + }, [control.id, peripheralId, send]); + + const press = useCallback(() => { + if (disabled || pressedRef.current) return; + pressedRef.current = true; + send(peripheralId, control.id, true); + }, [control.id, disabled, peripheralId, send]); + + useEffect(() => release, [release]); + useEffect(() => { + if (disabled) release(); + }, [disabled, release]); + + return ( + + ); +} + +function NumberControl({ peripheralId, control, disabled, send, value: storedValue }) { + const minimum = integerBound(control.min, 0); + const maximum = integerBound(control.max, minimum); + const initialValue = Number.isInteger(storedValue) + ? clampInteger(storedValue, minimum, maximum) + : minimum; + const [value, setValue] = useState(String(initialValue)); + const lastSentRef = useRef(initialValue); + + const commit = () => { + const next = clampInteger(value, minimum, maximum); + setValue(String(next)); + if (disabled || next === lastSentRef.current) return; + lastSentRef.current = next; + send(peripheralId, control.id, next); + triggerTouchHaptic('button'); + }; + + return ( + + ); +} + +function TextControl({ peripheralId, control, disabled, send, value: storedValue }) { + const maximumLength = Math.max(1, integerBound(control.maxLength, 1)); + const initialValue = typeof storedValue === 'string' + ? trimUnicode(storedValue, maximumLength) + : ''; + const [value, setValue] = useState(initialValue); + const lastSentRef = useRef(initialValue); + + const commit = () => { + const next = trimUnicode(value, maximumLength); + setValue(next); + if (disabled || next === lastSentRef.current) return; + lastSentRef.current = next; + send(peripheralId, control.id, next); + triggerTouchHaptic('button'); + }; + + return ( + + ); +} + +export default function AccessoryControlField(props) { + switch (props.control?.type) { + case 'slider': + return ; + case 'button': + return props.control.mode === 'momentary' + ? + : ; + case 'number': + return ; + case 'text': + return ; + default: + // roverd validates the four-type contract before publishing metadata. + // Returning nothing remains a defensive boundary for stale servers. + return null; + } +} diff --git a/webui/src/components/RoverAccessoryControls/index.jsx b/webui/src/components/RoverAccessoryControls/index.jsx new file mode 100644 index 00000000..064a0393 --- /dev/null +++ b/webui/src/components/RoverAccessoryControls/index.jsx @@ -0,0 +1,73 @@ +// Rover Accessory Controls +// Purpose: Renders every generic control advertised by a selected rover as one ordered control surface. +// Scope: Reusable content only; mobile and desktop parents own placement, expansion, and visibility. +import { useCallback } from 'react'; +import { useControlActions, useControlSelector } from '../../controls/index.js'; +import useCanControlRover from '../../hooks/useCanControlRover.js'; +import AccessoryControlField from './AccessoryControlField.jsx'; +import useRoverAccessories from './useRoverAccessories.js'; + +const EMPTY_ACCESSORY_VALUES = Object.freeze({}); + +export default function RoverAccessoryControls({ + roverId, + headerAction = null, + plainCenteredHeadings = false, + className = '', +}) { + const { peripherals } = useRoverAccessories(roverId); + const canControl = useCanControlRover(roverId); + const { setPeripheralControl } = useControlActions(); + const values = useControlSelector( + (control) => control.state.peripheralValues?.[String(roverId)] || EMPTY_ACCESSORY_VALUES, + ); + const send = useCallback( + (peripheralId, controlId, value) => setPeripheralControl(peripheralId, controlId, value), + [setPeripheralControl], + ); + + if (peripherals.length === 0) return null; + + return ( +
+ {peripherals.map((peripheral, peripheralIndex) => { + return ( +
+ {/* The firmware's array order is authoritative. Mapping directly over + it keeps physical authoring order intact across every UI host. + Every peripheral keeps its heading even when it is the only + device, because its firmware-provided name identifies which + physical accessory owns the controls below it. */} +
+ {/* Desktop already supplies one continuous HUD background, so + its title needs neither a second tone nor left alignment. + Mobile keeps the ordinary heading because it also carries + the Back action on the opposite side. */} +

+ {peripheral.name} +

+ {peripheralIndex === 0 ? headerAction : null} +
+
+ {peripheral.controls.map((control) => ( + + ))} +
+
+ ); + })} +
+ ); +} diff --git a/webui/src/components/RoverAccessoryControls/useRoverAccessories.js b/webui/src/components/RoverAccessoryControls/useRoverAccessories.js new file mode 100644 index 00000000..2ce305e2 --- /dev/null +++ b/webui/src/components/RoverAccessoryControls/useRoverAccessories.js @@ -0,0 +1,29 @@ +// Rover Accessories Selector +// Purpose: Provides the ordered generic-control inventory advertised by one rover. +// Scope: Selects public roster metadata only; placement and visibility remain parent-UI decisions. +import { useMemo } from 'react'; +import { useSessionSelector } from '../../context/SessionContext.jsx'; + +export default function useRoverAccessories(roverId) { + const rosterEntry = useSessionSelector((state) => { + if (!roverId) return null; + const roster = Array.isArray(state.session?.roster) ? state.session.roster : []; + return roster.find((entry) => String(entry.id) === String(roverId)) || null; + }); + const advertisedPeripherals = rosterEntry?.peripherals; + + const peripherals = useMemo(() => { + if (!Array.isArray(advertisedPeripherals)) return []; + // A peripheral with no generic controls may still provide a standardized + // camera, headlight, or laser backend. Those roles use their established + // HUD controls and must not create an empty Accessories surface. + return advertisedPeripherals.filter( + (peripheral) => Array.isArray(peripheral?.controls) && peripheral.controls.length > 0, + ); + }, [advertisedPeripherals]); + + return { + peripherals, + hasAccessories: peripherals.length > 0, + }; +} diff --git a/webui/src/controls/ControlContext.jsx b/webui/src/controls/ControlContext.jsx index 079f29da..80e1d8a6 100644 --- a/webui/src/controls/ControlContext.jsx +++ b/webui/src/controls/ControlContext.jsx @@ -66,6 +66,7 @@ const CONTROL_ACTION_NAMES = [ 'sendSong', 'startHorn', 'stopHorn', + 'setPeripheralControl', 'setMicPttActive', ]; @@ -724,6 +725,29 @@ export function ControlSystemProvider({ children }) { dispatch({ type: 'control/set-mic-ptt', payload: Boolean(active) }); }, []); + const setPeripheralControl = useCallback( + (peripheralId, controlId, value) => { + const sent = pipeline.sendPeripheralControl(peripheralId, controlId, value); + if (sent) { + // The generic protocol is currently command-only. Recording the value + // here lets every renderer instance share the browser's latest intent + // without pretending that it is device-reported telemetry. + dispatch({ + type: 'control/set-peripheral-value', + payload: { + roverId: pipeline.roverId, + peripheralId, + controlId, + value, + }, + }); + recordControlIntent(); + } + return sent; + }, + [pipeline, recordControlIntent], + ); + const actionImplementations = useMemo( () => ({ setMode, @@ -751,6 +775,7 @@ export function ControlSystemProvider({ children }) { sendSong, startHorn, stopHorn, + setPeripheralControl, setMicPttActive, }), [ @@ -779,6 +804,7 @@ export function ControlSystemProvider({ children }) { sendSong, startHorn, stopHorn, + setPeripheralControl, setMicPttActive, ], ); diff --git a/webui/src/controls/commandPipeline.js b/webui/src/controls/commandPipeline.js index 6bf86f39..188d7536 100644 --- a/webui/src/controls/commandPipeline.js +++ b/webui/src/controls/commandPipeline.js @@ -44,6 +44,11 @@ export function useCommandPipeline(options = {}) { return rosterEntry.horn; }, [rosterEntry]); + const peripherals = useMemo( + () => (Array.isArray(rosterEntry?.peripherals) ? rosterEntry.peripherals : []), + [rosterEntry], + ); + const headlightState = useMemo(() => rosterEntry?.headlight?.state ?? null, [rosterEntry]); const laserState = useMemo(() => rosterEntry?.laser?.state ?? null, [rosterEntry]); const emitCommand = useCallback( @@ -208,6 +213,22 @@ export function useCommandPipeline(options = {}) { [emitCommand, roverId], ); + const sendPeripheralControl = useCallback( + (peripheralId, controlId, value) => { + if (!roverId || !peripheralId || !controlId) return null; + const peripheral = { id: peripheralId, control: controlId, value }; + // Peripheral commands deliberately use the same command envelope as all + // other rover actuation. This keeps turn authorization, acknowledgements, + // and rover WebSocket routing in the server's existing command boundary. + emitCommand({ + type: 'peripheral', + data: { peripheral }, + }); + return peripheral; + }, + [emitCommand, roverId], + ); + const sendSong = useCallback( (notes = [], options = {}) => { if (!roverId) return null; @@ -244,6 +265,7 @@ export function useCommandPipeline(options = {}) { laser, laserState, horn, + peripherals, emitCommand, enableSensorStream, sendDriveDirect, @@ -253,6 +275,7 @@ export function useCommandPipeline(options = {}) { sendHeadlight, sendLaser, sendHorn, + sendPeripheralControl, sendSong, runMacroSteps, }), @@ -265,6 +288,7 @@ export function useCommandPipeline(options = {}) { laser, laserState, horn, + peripherals, emitCommand, enableSensorStream, sendDriveDirect, @@ -274,6 +298,8 @@ export function useCommandPipeline(options = {}) { sendHeadlight, sendLaser, sendHorn, + sendPeripheralControl, + sendSong, runMacroSteps, ], ); diff --git a/webui/src/controls/controlReducer.js b/webui/src/controls/controlReducer.js index 841ff18d..cc325513 100644 --- a/webui/src/controls/controlReducer.js +++ b/webui/src/controls/controlReducer.js @@ -69,6 +69,10 @@ export const initialControlState = { macros: DEFAULT_MACROS, keymap: DEFAULT_KEYMAP, inputs: {}, + // Generic accessory controls do not currently report state back from roverd. + // Keep the last browser-issued value per rover/device/control so closing a + // drawer or changing responsive layouts does not make the UI lie about it. + peripheralValues: {}, }; export function controlReducer(state, action) { @@ -226,6 +230,27 @@ export function controlReducer(state, action) { active: Boolean(action.payload), }, }; + case 'control/set-peripheral-value': { + const roverId = String(action.payload?.roverId || ''); + const peripheralId = String(action.payload?.peripheralId || ''); + const controlId = String(action.payload?.controlId || ''); + if (!roverId || !peripheralId || !controlId) return state; + const roverValues = state.peripheralValues?.[roverId] || {}; + const peripheralValues = roverValues[peripheralId] || {}; + return { + ...state, + peripheralValues: { + ...(state.peripheralValues || {}), + [roverId]: { + ...roverValues, + [peripheralId]: { + ...peripheralValues, + [controlId]: action.payload.value, + }, + }, + }, + }; + } default: return state; }