diff --git a/.gitignore b/.gitignore index 5bc85b8..d7badac 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ pytest-*/* build-*/* .ignore src/firmware/platform/pico/switch2_wake_config.h +node_modules +.npm diff --git a/README.md b/README.md index 6297d9d..d7f6f23 100644 --- a/README.md +++ b/README.md @@ -350,7 +350,47 @@ Output mode is selected before TinyUSB starts and never changes while mounted. A Development USB identities are `CAFE:4010` (XInput), `CAFE:4020` (DInput), and `CAFE:4021` (Mac). DInput and Mac expose four input-only generic HID interfaces and no rumble. Mac uses X/Y/Z/Rx sticks plus Simulation Brake/Accelerator triggers. Switch reports input, rumble, and motion capability; XInput reports input and rumble. -`profiles edit` starts a local-only browser editor at `http://127.0.0.1:8765/`. It exposes every profile field: all 16 buttons plus the L2/R2 analog triggers can be remapped to ordinary buttons, triggers, or Switch SL/SR rail outputs; both sticks and triggers retain independent deadzone/saturation/curve settings; and rumble, confirmation, Turbo/Auto Burst, built-in action chords, and four custom macro sequences are editable. Its live playtest compares current raw stick and trigger input with the unsaved draft, shows deadzone/saturation boundaries and digital thresholds, and highlights pressed physical controls. Select a controller identity and one of its eight profile slots, use **Start from defaults** for a new draft, then **Save to Pico**. The backend validates the complete profile before using the existing chunked atomic transaction; invalid drafts never reach flash. Use `profiles edit --no-browser` for a printed URL or `profiles edit --port PORT` to choose another local port. +`profiles edit` starts **Controller Studio**, a local-only browser editor at +`http://127.0.0.1:8765/`. Select a controller and one of its eight profile slots +in the sidebar. The editor has seven focused sections: **Button mapping**, +**Sticks & triggers**, **Shift layer**, **Profile shortcuts**, **Rumble**, +**Turbo**, and **Actions & macros**. Section links support keyboard navigation, +browser history, and direct URLs such as `/#analog`; switching sections keeps +the current draft. **Reset defaults** starts a new default draft, +**Save to Pico** stores it, and **Make active** separately selects it for play. +Rename and controller alias actions save their metadata immediately; Copy +replaces the destination profile and name, including any unsaved draft settings. + +The editor exposes every profile field: button, analog-trigger and rail-output +mappings; independent stick/trigger deadzones, saturation and response curves; +rumble and confirmation; Turbo/Auto Burst; action chords; and four custom macros. +Live playtest compares controller input with the draft and highlights pressed +controls. Detailed diagram, native-output and adapter help is expandable. +Saving an invalid field opens its section and focuses the field. The backend +validates the complete profile before the existing chunked atomic transaction; +invalid drafts never reach flash. Use `profiles edit --no-browser` for a printed +URL or `profiles edit --port PORT` to choose another local port. + +USB failures clear the adapter connection indicator and disable device actions +without discarding the draft. Discovery checks once per request; it does not +hold the editor's single-threaded server for the transaction timeout while the +Pico is absent. Background tabs show **Connection check paused** and recheck on +return. Controller disconnection and unsupported live-input features are +separate from adapter availability. + +**Studio styling:** Tailwind CSS is compiled locally and the resulting +`src/switch_pico_bridge/web/profile_editor.css` ships with the Python package. +Running Studio needs neither Node.js nor an internet connection. When changing +the HTML, JavaScript class names, or `web/input.css`, rebuild the stylesheet from +the repository root using the pinned development dependencies: + +```sh +npm ci +npm run build:css +``` + +`npm run watch:css` rebuilds while developing. Include the generated +`profile_editor.css` with source changes; no Tailwind CDN is used at runtime. Switch 2's **C, GL, GR, Left SL/SR and Right SL/SR** are additional input controls. Base and Shift mappings can route them to ordinary buttons, triggers, or rail outputs; they also remain available in action/macro chords, cancellation and modifiers. Extra input mappings default to disabled. **Left SL, Left SR, Right SL and Right SR** are real output destinations in both native Joy-Con and Switch Pro reports. C/GL/GR remain input sources, not destinations. XInput and generic HID have no rail-button equivalents. Macro and swing output actions retain their existing ordinary-button contract. @@ -358,6 +398,13 @@ Controller Studio uses the supplied lightweight SVGs for Switch 2 Pro, Joy-Con 2 **Auto** uses matching-owner live metadata to distinguish Joy-Con pairs/solo halves and Wii horizontal, vertical, and Nunchuk layouts. **Preview** changes only the editor's diagram and source labels; it does not pair controllers or change saved mappings, and physical highlighting is disabled. Source choices reflect the layout while stored unavailable mappings are retained. Legacy Wii metadata without orientation uses a visibly labeled horizontal reference with physical highlighting disabled. +Disconnected Wii-family owners use a labeled Wii Remote reference instead of +the generic gamepad artwork. The reference shows default horizontal controls, +keeps every source mapping editable, and does not highlight physical input. +Saved Wii-family identifiers cannot establish orientation, extensions, or +distinguish a Wii U Pro Controller. Matching live metadata replaces the reference +with the detected layout; reconnecting or disconnecting never changes the draft. + **Wii orientation** is separate from layout preview. Select a connected Wii Remote owner, choose **Horizontal** or **Vertical**, and click **Apply orientation**. Studio waits for firmware confirmation before reporting success. This changes the current connection's physical button mapping, not saved profiles or adapter configuration. Nunchuk mappings remain vertical while attached; unplugging restores the selected standalone orientation. Changing orientation clears old held-input/macro/capture state and advances the logical input generation without reconnecting Bluetooth. Reconnect uses the horizontal default, or vertical if + is held while connecting. **Native Joy-Con output layout:** for a Pro Controller, DualSense or other full @@ -378,7 +425,7 @@ profile, then assign the controller in Switch **Change Grip/Order** with the mapped SL+SR combination. Keep existing game profiles intact rather than replacing them. Console/game compatibility still requires hardware qualification. -**Swap left & right sticks (including clicks)** is in Analog. It works in both +**Swap left & right sticks (including clicks)** is in **Sticks & triggers**. It works in both native Joy-Con and ordinary Pro-emulation firmware (and other output modes): each physical stick is calibrated first, then the output axis pairs and mapped stick clicks exchange sides. Final-output macro overrides remain downstream. @@ -399,7 +446,7 @@ swapping**, before native sideways rotation. Opposite directions cancel per axis. Cardinals use full scale; diagonals are normalized inside the stick's radius. Any nonzero mapped-left analog vector takes priority over the entire digital vector. If analog drift prevents D-pad movement, adjust the physical -stick's calibration/inner deadzone in Analog; no hidden deadzone is added. +stick's calibration/inner deadzone in **Sticks & triggers**; no hidden deadzone is added. Movement releases when its sources release and never presses the stick click. Final-output macro overrides retain their existing downstream behavior. @@ -512,7 +559,7 @@ The AIO firmware enables motion automatically for original Wii Remotes with an e - **Nunchuk stick travel:** the parser reads the 16-byte extension calibration at `A40020` before activating MotionPlus. It validates both checksum bytes and the X/Y min/center/max ordering, then scales each side of each axis independently into the full normalized range, clamps overshoot, and inverts Y. It preserves the notched gate rather than expanding diagonals into square corners. Unreadable/invalid calibration uses documented nominal travel (center 128, ±96), not the full 0–255 byte range. Replacement Nunchuks reload their own calibration. Existing left-stick profile tuning applies; saved right-stick tuning is not copied or overwritten. - **Motion:** ordinary remotes use continuous `0x31`; MotionPlus and Nunchuk use combined `0x35` reports. The parser checks primary/backup accelerometer factory calibration and both fast/slow gyro calibration blocks, including checksums. Invalid calibration disables only the affected sensor rather than inventing readings. MotionPlus selects sensitivity independently for each axis; factory-only zero correction can retain temperature-dependent gyro drift. - **Nunchuk passthrough:** MotionPlus and Nunchuk samples alternate. Held Nunchuk controls persist across gyro packets, moved C/Z bits are decoded, and the remote's accelerometer remains the motion source. Attaching or removing a Nunchuk triggers serialized extension discovery, updates Studio's layout, and clears detached stick/C/Z state. Extension identifiers accept the upstream-compatible type suffix rather than requiring a vendor-specific prefix; initializing extensions get bounded reply-paced retries. Periodic discovery of an inactive external MotionPlus attached alone and Classic Controller MotionPlus passthrough remain unimplemented. Existing ordinary Classic Controller, Wii U Pro, Balance Board and uDraw paths remain separate. -- **Motion gesture bindings:** Studio's Macro section provides **Wii Remote swing**, **Nunchuk swing**, and **Both together**. Each can select a final output button or one of the four configured macros, plus an optional held modifier. Remote and Nunchuk have independent Low/Medium/High sensitivity. All new bindings default to disabled; existing Remote bindings are preserved. No sensor bar or MotionPlus is required, and gestures remain available when gyro output is disabled. For Zelda on Switch, choose **Y** (logical `west`; use the Switch preview for Nintendo button labels). +- **Motion gesture bindings:** Studio's **Actions & macros** section provides **Wii Remote swing**, **Nunchuk swing**, and **Both together**. Each can select a final output button or one of the four configured macros, plus an optional held modifier. Remote and Nunchuk have independent Low/Medium/High sensitivity. All new bindings default to disabled; existing Remote bindings are preserved. No sensor bar or MotionPlus is required, and gestures remain available when gyro output is disabled. For Zelda on Switch, choose **Y** (logical `west`; use the Switch preview for Nintendo button labels). - **Combined priority:** when Both together is enabled and both calibrated sensors are fresh, individual gestures wait for the combination window (30–200 ms, default 100 ms). One full swing can pair with smaller sustained movement from the other sensor, before or after it within the inclusive window. Confirmation uses half that sensor's normal acceleration threshold while retaining the force/tilt guard and fresh-sample evidence requirement; individual thresholds are unchanged. Two small movements alone do not trigger an action. Confirmation is consumed once, including the confirming source's rearm/cooldown, so a later full swing from that same stroke cannot leak an individual action. Without an eligible combined binding or a fresh Nunchuk stream, Remote actions do not acquire the delay. Disconnects, lost modifiers, stale samples and active macros discard pending evidence rather than replaying it. - **Stroke detection and macros:** initially settle each enabled sensor for 120 ms. Further strokes need only a 20 ms lower-force gap and at least 200 ms between presses; early rebounds are discarded. Buttons generate 80 ms presses. Gesture macros run one cycle regardless of their physical-trigger playback mode and must contain a positive-duration step before binding. Physical macro triggers and cancel controls take priority; gestures during active macros are ignored, not queued. The detector retains stroke history so repeated gesture macros do not require full stops. Profile changes and reserved hotkeys cancel pending gesture output; physical button holds remain intact. - **Independent Nunchuk acceleration:** plain and MotionPlus passthrough reports use separate calibrated samples and freshness counters. Nunchuk acceleration never replaces the Remote's console IMU. Invalid factory accelerometer calibration disables Nunchuk/combined gestures without disabling the stick, C/Z buttons, or Remote gestures; unplugging and replacement discard the old Nunchuk sample. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..902c697 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1130 @@ +{ + "name": "switch-pico-profile-editor", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "switch-pico-profile-editor", + "version": "1.0.0", + "devDependencies": { + "@tailwindcss/cli": "4.3.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.3.1.tgz", + "integrity": "sha512-ZWPy20rF+TBfTImxDMG3Wr75Y3RpaPlo9lc+oJbInlMyjT+XPkTVKVIL5RZ7JirXuIahcfHoLNFRmDorKi+JQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "2.5.1", + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "enhanced-resolve": "5.21.6", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.3.1" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dd88978 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "switch-pico-profile-editor", + "private": true, + "version": "1.0.0", + "description": "Tailwind CSS toolchain for Switch Pico Profile Studio", + "scripts": { + "build:css": "tailwindcss -i ./src/switch_pico_bridge/web/input.css -o ./src/switch_pico_bridge/web/profile_editor.css --minify", + "watch:css": "tailwindcss -i ./src/switch_pico_bridge/web/input.css -o ./src/switch_pico_bridge/web/profile_editor.css --watch" + }, + "devDependencies": { + "@tailwindcss/cli": "4.3.1", + "tailwindcss": "4.3.1" + } +} diff --git a/src/switch_pico_bridge/profile_web.py b/src/switch_pico_bridge/profile_web.py index bbb8280..2f002e1 100644 --- a/src/switch_pico_bridge/profile_web.py +++ b/src/switch_pico_bridge/profile_web.py @@ -184,9 +184,10 @@ def _controller_presentation( (0x054C, 0x0CE6): ("Sony DualSense", "playstation", "dualsense"), (0x054C, 0x0DF2): ("Sony DualSense Edge", "playstation", "dualsense"), (0x054C, 0x09CC): ("Sony DualShock 4", "playstation", "generic"), - # VID/PID cannot distinguish Wii extensions or Wii U Pro from Remote Plus. - (0x057E, 0x0306): ("Nintendo Wii controller", "generic", "generic"), - (0x057E, 0x0330): ("Nintendo Wii / Wii U controller", "generic", "generic"), + # Wii-family IDs do not establish orientation, extensions, or Wii U Pro. + # Use family reference artwork without narrowing the available sources. + (0x057E, 0x0306): ("Nintendo Wii controller", "wii", "wii-reference"), + (0x057E, 0x0330): ("Nintendo Wii / Wii U controller", "wii", "wii-reference"), } exact = known.get((identity.vendor_id, identity.product_id)) if exact is not None: @@ -278,8 +279,10 @@ class ProfileEditorServer(HTTPServer): def find_device(self) -> config_manager.UsbDevice: if self._device is None: + # Browser polling handles reconnection. Waiting the transaction + # timeout here blocks every request on this single-threaded server. self._device = config_manager.find_pico( - self.bus, self.device_address, self.operation_timeout + self.bus, self.device_address, timeout=0.0 ) return self._device diff --git a/src/switch_pico_bridge/web/controller_layouts.js b/src/switch_pico_bridge/web/controller_layouts.js index 2493412..fecdb42 100644 --- a/src/switch_pico_bridge/web/controller_layouts.js +++ b/src/switch_pico_bridge/web/controller_layouts.js @@ -193,5 +193,11 @@ const ControllerLayouts = (() => { note: "Nunchuk stick uses LEFT axes with calibrated travel and has no click. B→south, A→east, 1/2→left/right shoulder, Nunchuk C→west and Z→north. Nunchuk C is a normal face source, not the Switch 2 C extra. Power is not remappable.", }, }; + layouts["wii-reference"] = { + ...layouts["wii-remote"], + name: "Wii Remote · reference layout", + referenceOnly: true, + note: "Wii-family reference, shown with the default horizontal controls. The saved identity cannot confirm orientation, extensions, or distinguish a Wii U Pro Controller. Connect the controller for its detected layout, or choose a diagram preview. All source mappings remain available; physical highlighting is off.", + }; return layouts; })(); diff --git a/src/switch_pico_bridge/web/input.css b/src/switch_pico_bridge/web/input.css new file mode 100644 index 0000000..fa42d5c --- /dev/null +++ b/src/switch_pico_bridge/web/input.css @@ -0,0 +1,350 @@ +@import "tailwindcss" source(none); +@source "./profile_editor.html"; +@source "./profile_editor.js"; + +@theme { + --color-slate-950: #0b1019; + --color-slate-900: #121a27; + --color-slate-850: #17202e; + --color-slate-800: #202c3e; + --color-slate-700: #3c4c63; + --color-blue-500: #80afff; + --color-blue-600: #427cdd; + --color-sky-500: #80afff; + --color-emerald-400: #6ed4b1; + --color-amber-400: #ebc174; + --color-rose-400: #f19aaa; +} + +@layer base { + :root { color-scheme: dark; } + body { @apply text-sm; } + [hidden] { display: none !important; } + h2, h3, h4 { @apply font-semibold tracking-tight text-slate-100; } + h2, h3 { @apply text-xl; } + h4 { @apply text-base; } + button, select, summary { @apply cursor-pointer; } + button:disabled, select:disabled, input:disabled { @apply cursor-not-allowed opacity-45; } + :is(button, input, select, a, summary, [tabindex]):focus-visible { outline: 2px solid var(--color-blue-500); outline-offset: 3px; } + input[type="range"], progress { accent-color: var(--color-blue-500); } + input[type="range"] { @apply w-full; } + input, select, fieldset { @apply min-w-0; } + progress { @apply h-2 w-full overflow-hidden rounded-full; } + progress::-webkit-progress-bar { @apply rounded-full bg-slate-800; } + progress::-webkit-progress-value { @apply rounded-full bg-blue-500; } + * { scrollbar-width: thin; scrollbar-color: var(--color-slate-700) transparent; } +} + +@layer components { + .topbar { @apply sticky top-0 z-30 flex min-h-20 items-center justify-between gap-6 border-b border-slate-800 bg-slate-950 px-6; } + .brand { @apply flex items-center gap-3; } + .brand-mark { @apply grid size-9 shrink-0 grid-cols-2 gap-1 rounded-lg border border-blue-500/30 bg-blue-500/10 p-2; } + .brand-mark span { @apply rounded-sm bg-blue-500; } + .brand-mark span:last-child { @apply bg-blue-500/50; } + .topbar-meta { @apply flex items-center gap-4; } + .connection { @apply flex min-w-0 items-center gap-2 text-xs text-slate-300; } + .connection-dot { @apply size-2 shrink-0 rounded-full bg-amber-400; } + .connection[data-state="ready"] .connection-dot { @apply bg-emerald-400; } + .connection[data-state="error"] .connection-dot { @apply bg-rose-400; } + .workspace { @apply mx-auto my-6 grid w-full max-w-[1720px] grid-cols-[248px_minmax(0,1fr)] items-start gap-6 px-6; } + .sidebar { @apply sticky top-26 max-h-[calc(100dvh-120px)] overflow-y-auto rounded-xl border border-slate-800 bg-slate-900 p-5; } + .sidebar-heading, .adapter-settings-heading { @apply flex items-center justify-between gap-3; } + .eyebrow { @apply mb-1 text-[11px] font-semibold tracking-wider text-slate-400 uppercase; } + .icon-button { @apply flex size-9 shrink-0 items-center justify-center rounded-lg border border-slate-700 text-slate-300 hover:bg-slate-800; } + .field-label { @apply mt-5 mb-2 block text-xs font-medium text-slate-300; } + .field-help, .help-details p { @apply mt-2 text-xs leading-relaxed text-slate-400; } + .identity-actions { @apply mt-2 flex flex-wrap gap-2; } + .identity-actions .button { @apply flex-1 px-2; } + .controller-details { @apply mt-3 text-xs leading-relaxed text-slate-400; overflow-wrap: anywhere; } + .sidebar-note { @apply mt-5 flex gap-2 border-t border-slate-800 pt-4 text-xs leading-relaxed text-slate-400; } + .note-icon { @apply flex size-4 shrink-0 items-center justify-center rounded-full border border-slate-700 text-[10px]; } + .profile-list { @apply grid gap-1.5; } + .profile-button { @apply flex min-h-11 items-center justify-between gap-2 rounded-lg border border-transparent border-l-[3px] px-3 py-2 text-left text-slate-300 hover:bg-slate-800; border-left-color: var(--profile-color); } + /* Slot colors follow kProfileLightbarPalette in bluepad32_input_backend.cpp. */ + .profile-button[data-profile-index="0"] { --profile-color: #0055ff; } + .profile-button[data-profile-index="1"] { --profile-color: #00cc66; } + .profile-button[data-profile-index="2"] { --profile-color: #ffaa00; } + .profile-button[data-profile-index="3"] { --profile-color: #cc33ff; } + .profile-button[data-profile-index="4"] { --profile-color: #ff4444; } + .profile-button[data-profile-index="5"] { --profile-color: #00dddd; } + .profile-button[data-profile-index="6"] { --profile-color: #ff66bb; } + .profile-button[data-profile-index="7"] { --profile-color: #ccff33; } + .profile-button.selected { @apply text-slate-100; border-color: var(--profile-color); background: color-mix(in srgb, var(--profile-color) 12%, var(--color-slate-900)); } + .profile-number { @apply flex min-w-0 items-center gap-3 text-xs font-medium; overflow-wrap: anywhere; } + .profile-number span { @apply flex size-6 shrink-0 items-center justify-center rounded text-xs font-semibold text-slate-950; background: var(--profile-color); } + .profile-button[data-profile-index="0"] .profile-number span { @apply text-white; } + .mini-active { @apply shrink-0 text-[10px] font-medium text-emerald-400; } + .editor-shell { @apply min-w-0; } + .editor-header { @apply mb-4 flex flex-wrap items-center justify-between gap-5 rounded-xl border border-slate-800 bg-slate-900 p-5; } + .title-line { @apply flex-wrap; } + .profile-name-editor { @apply mt-3 flex items-center gap-2; } + .profile-name-editor input { @apply h-10 w-full max-w-60 rounded-lg border border-slate-700 bg-slate-950 px-3 text-sm; } + .header-actions { @apply flex flex-col items-end gap-2; } + .action-group-primary { order: -1; } + .active-badge, .dirty-badge { @apply inline-flex rounded-md border px-2 py-0.5 text-[11px] font-medium; } + .active-badge { @apply border-emerald-400/20 bg-emerald-400/10 text-emerald-400; } + .dirty-badge { @apply border-amber-400/20 bg-amber-400/10 text-amber-400; } + .button { @apply inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border border-transparent px-3 py-2 text-xs font-semibold transition-colors; } + .button-primary { @apply border-blue-500 bg-blue-500 text-slate-950 hover:bg-blue-400; } + .button-secondary { @apply border-slate-700 bg-slate-800/50 text-slate-200 hover:bg-slate-800; } + .button-ghost { @apply text-slate-400 hover:bg-slate-800 hover:text-slate-100; } + .select, .number-input { @apply w-full min-w-0 rounded-lg border border-slate-700 bg-slate-950 px-3 py-2.5 text-sm text-slate-100; } + .section-nav { @apply sticky top-20 z-20 mb-4 flex gap-1 overflow-x-auto border-b border-slate-700 bg-slate-950 py-2; } + .section-nav a { @apply shrink-0 rounded-lg px-3 py-2.5 text-xs font-medium text-slate-400 hover:bg-slate-800 hover:text-slate-100; } + .section-nav a[aria-current="page"] { @apply bg-blue-500/15 text-blue-500; } + .panel { @apply mb-6 min-w-0 scroll-mt-40 rounded-xl border border-slate-800 bg-slate-900 p-6; } + .panel-heading { @apply mb-6 flex items-start justify-between gap-5; } + .panel-heading-actions { @apply flex max-w-sm flex-col items-end gap-2 text-right; } + .panel-heading-actions p { @apply text-xs leading-relaxed text-slate-400; } + .section-reset { @apply min-h-8 text-xs font-medium text-blue-500 hover:underline; } + .help-details { @apply mt-4 border-t border-slate-800 pt-3; } + .help-details summary, .joycon-mode-help summary { @apply text-xs font-medium text-blue-500; } + .help-details strong { @apply text-slate-200; } + .controller-mapper { @apply grid grid-cols-[minmax(0,1fr)_240px] items-start gap-4; } + .controller-canvas { @apply min-w-0 overflow-hidden rounded-xl border border-slate-800 bg-slate-950 p-4; } + .controller-caption { @apply flex flex-wrap items-center justify-between gap-2; } + .controller-caption strong { @apply text-sm font-medium; } + .controller-live { @apply text-[11px] text-amber-400; } + .controller-live[data-state="live"] { @apply text-emerald-400; } + .controller-viewport { @apply max-w-full overflow-x-auto overflow-y-hidden py-4; } + .controller-photo-wrap { position: relative; width: 100%; min-width: var(--diagram-min, 640px); max-width: var(--diagram-max, 900px); margin: auto; aspect-ratio: var(--diagram-ratio, 800 / 552); } + .controller-stage { position: absolute; top: 50%; left: 50%; width: var(--stage-width, 100%); height: var(--stage-height, 100%); transform: translate(-50%, -50%) rotate(var(--stage-angle, 0deg)); } + .controller-photo { @apply block size-full pointer-events-none select-none; } + .controller-hotspots { @apply absolute inset-0; } + .controller-hotspots button { @apply absolute grid size-[30px] place-items-center rounded-full border-2 border-blue-500 bg-slate-950/95 p-0 text-[10px] leading-none font-bold text-slate-100; left: var(--x, 50%); top: var(--y, 50%); transform: translate(-50%, -50%) rotate(var(--label-angle, 0deg)); } + .controller-hotspots button:hover { @apply z-10 bg-slate-800; } + .controller-hotspots button.selected { @apply z-10 border-blue-500 bg-blue-500 text-slate-950 ring-4 ring-blue-500/20; } + .controller-hotspots button.disabled-map:not(.selected):not(.pressed) { @apply border-slate-500 text-slate-400; } + .controller-hotspots button.pressed { @apply z-20 border-emerald-400 bg-emerald-400 text-slate-950 ring-4 ring-emerald-400/20; } + .controller-hotspots button[data-controller-button^="dpad_"] { @apply size-[26px]; } + .controller-annotation { @apply absolute max-w-40 rounded border border-slate-500 bg-slate-800 p-1.5 text-center text-[11px]; left: var(--x); top: var(--y); transform: translate(-50%, -50%) rotate(var(--label-angle, 0deg)); } + .controller-hint { @apply mt-2 text-center text-xs text-slate-400; } + .extra-controls { @apply mt-2 flex flex-wrap gap-2; } + .extra-controls button { @apply min-h-11 min-w-11 rounded-lg border border-blue-500/50 bg-slate-800 px-3 py-2 text-xs font-medium; } + .extra-controls button.disabled-map { @apply border-slate-700 text-slate-400; } + .extra-controls button.selected { @apply ring-2 ring-blue-500; } + .extra-controls button.pressed { @apply border-emerald-400 bg-emerald-400 text-slate-950; } + .source-unavailable { @apply text-amber-400; } + .mapping-inspector { @apply min-w-0 rounded-xl border border-blue-500/25 bg-slate-850 p-5; } + .selected-control-mark { @apply my-4 grid size-12 place-items-center rounded-xl bg-blue-500/15 text-lg font-bold text-blue-500; } + .mapping-inspector > p:not(.eyebrow) { @apply mt-2 text-xs leading-relaxed text-slate-400; } + .mapping-key { @apply mt-6 flex flex-wrap gap-4 text-xs text-slate-400; } + .mapping-key span { @apply inline-flex items-center gap-2; } + .key-dot { @apply size-2 rounded-full border-2 border-blue-500; } + .key-dot.disabled { @apply border-slate-500; } + .native-joycon-control, .wii-orientation-control { @apply mt-5 rounded-xl border border-slate-700 bg-slate-850/50 p-4; } + .native-joycon-control > .field-label, .wii-orientation-control > .field-label { @apply mt-0; } + .native-joycon-actions, .wii-orientation-actions { @apply flex flex-wrap items-center gap-2; } + .native-joycon-actions .select, .wii-orientation-actions .select { @apply flex-[1_1_240px]; } + .joycon-mode-status, .wii-orientation-status { @apply mt-3 text-xs leading-relaxed text-emerald-400; overflow-wrap: anywhere; } + .wii-orientation-status[data-state="idle"] { @apply hidden; } + :is(.joycon-mode-status, .wii-orientation-status):is([data-state="loading"], [data-state="locked"]) { @apply text-slate-400; } + :is(.joycon-mode-status, .wii-orientation-status):is([data-state="pending"], [data-state="unsupported"]) { @apply text-amber-400; } + :is(.joycon-mode-status, .wii-orientation-status)[data-state="error"] { @apply text-rose-400; } + .mapping-grid { @apply grid grid-cols-4 gap-3; } + .modifier-controls { @apply mb-5 grid grid-cols-2 gap-4; } + .control-card, .action-card { @apply min-w-0 rounded-xl border border-slate-700/60 bg-slate-850 p-4; } + .control-card label, .number-field label { @apply mb-2 block text-xs font-medium text-slate-300; } + .analog-grid, .feedback-grid, .builtin-actions { @apply grid grid-cols-2 gap-4; } + .subpanel { @apply min-w-0 rounded-xl border border-slate-700/60 bg-slate-850 p-4; } + .subpanel-heading { @apply mb-4 flex flex-wrap items-start justify-between gap-3; } + .subpanel-heading > span { @apply max-w-60 text-xs leading-relaxed text-slate-400; } + .number-grid { @apply grid grid-cols-2 gap-3; } + .number-field.wide, .toggle-row { @apply col-span-full; } + .toggle-row, .check-grid { @apply flex flex-wrap gap-2; } + .checkbox-pill { @apply relative inline-block; } + .checkbox-pill input { @apply sr-only; } + .checkbox-pill span { @apply block rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-xs font-medium text-slate-300; } + .checkbox-pill input:checked + span { @apply border-blue-500/70 bg-blue-500/20 text-slate-100; } + .checkbox-pill input:focus-visible + span { outline: 2px solid var(--color-blue-500); outline-offset: 3px; } + .checkbox-pill input:disabled + span { @apply cursor-not-allowed opacity-40; } + .controller-choice span { @apply flex min-h-14 min-w-14 flex-col items-center justify-center gap-1 text-center; } + .controller-choice b { @apply text-base; } + .controller-choice small { @apply max-w-20 text-[10px]; } + .range-row { @apply mb-4; } + .range-meta { @apply mb-2 flex justify-between gap-2 text-xs text-slate-300; } + .range-meta output { @apply font-medium tabular-nums; } + .curve-editor { @apply mb-4 grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] gap-4 rounded-lg border border-slate-700/50 bg-slate-950 p-3; } + .curve-editor svg { @apply min-h-28 w-full overflow-visible border-b border-l border-slate-700; } + .curve-editor path { fill: none; vector-effect: non-scaling-stroke; } + .curve-line { stroke: var(--color-blue-500); stroke-width: 2.5; } + .curve-guide { stroke: var(--color-slate-500); stroke-dasharray: 3 3; opacity: 0.5; } + .curve-marker { fill: var(--color-amber-400); stroke: var(--color-slate-950); stroke-width: 2; opacity: 0; } + .curve-marker.visible { opacity: 1; } + .curve-controls { @apply flex min-w-0 flex-col gap-3; } + .curve-controls label { @apply text-xs text-slate-300; } + .curve-controls output { @apply float-right text-slate-100; } + .copy-side { @apply mt-auto; } + .stick-swap-panel { @apply mt-4; } + .playtest-panel { @apply mb-5 rounded-xl border border-blue-500/25 bg-slate-950/40 p-4; } + .playtest-heading, .playtest-card-heading { @apply flex flex-wrap items-center justify-between gap-2; } + .playtest-help { @apply my-3 text-xs leading-relaxed text-slate-400; } + .playtest-status { @apply rounded-md border border-current px-2 py-1 text-[11px] text-amber-400; } + .playtest-panel[data-state="live"] .playtest-status { @apply text-emerald-400; } + .playtest-panel[data-state="error"] .playtest-status { @apply text-rose-400; } + .playtest-panel[data-state="waiting"] .playtest-grid { @apply opacity-50; } + .playtest-grid { @apply grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1.4fr)] gap-3; } + .playtest-card { @apply min-w-0 rounded-lg border border-slate-700/50 bg-slate-850 p-3; } + .playtest-card-heading { @apply mb-3 text-xs; } + .playtest-card-heading span { @apply text-[11px] text-slate-400 tabular-nums; } + .stick-scope { --raw-x: 50%; --raw-y: 50%; --output-x: 50%; --output-y: 50%; --inner-size: 0%; --outer-size: 100%; @apply relative mx-auto aspect-square w-full max-w-40 overflow-hidden rounded-full border border-slate-700 bg-slate-950; } + .stick-axis { @apply absolute bg-slate-700/70; } + .stick-axis-x { @apply inset-x-[10%] top-1/2 h-px; } + .stick-axis-y { @apply inset-y-[10%] left-1/2 w-px; } + .stick-ring { @apply pointer-events-none absolute top-1/2 left-1/2 rounded-full border; transform: translate(-50%, -50%); } + .stick-ring-inner { width: var(--inner-size); height: var(--inner-size); @apply border-rose-400/80 bg-rose-400/5; } + .stick-ring-outer { width: var(--outer-size); height: var(--outer-size); @apply border-blue-500/60; } + .stick-dot { @apply absolute rounded-full; transform: translate(-50%, -50%); } + .stick-dot-raw { left: var(--raw-x); top: var(--raw-y); @apply z-10 size-3.5 border-2 border-amber-400; } + .stick-dot-output { left: var(--output-x); top: var(--output-y); @apply z-20 size-2 border border-slate-950 bg-blue-500; } + .trigger-meter { @apply mt-3 grid grid-cols-[24px_minmax(0,1fr)] items-center gap-2 text-xs; } + .trigger-meter output { @apply col-start-2 text-[11px] text-slate-400 tabular-nums; } + .trigger-track { --raw: 0%; --output: 0%; --threshold: 35%; @apply relative h-7 overflow-hidden rounded-md border border-slate-700 bg-slate-950; } + .trigger-bar { @apply absolute left-0 h-1.5 rounded-r; } + .trigger-bar-raw { width: var(--raw); @apply top-1 bg-amber-400; } + .trigger-bar-output { width: var(--output); @apply bottom-1 bg-blue-500; } + .trigger-threshold { left: var(--threshold); @apply absolute inset-y-0 w-0.5 bg-rose-400; } + .playtest-legend { @apply mt-4 flex flex-wrap gap-3 text-[11px] text-slate-400; } + .playtest-legend span { @apply inline-flex items-center gap-1.5; } + .playtest-legend i { @apply size-2 rounded-full; } + .legend-raw { @apply bg-amber-400; } + .legend-output { @apply bg-blue-500; } + .legend-threshold { @apply bg-rose-400; } + .mapping-playtest { @apply mt-4 space-y-2 text-xs text-slate-300; } + .turbo-grid { @apply grid grid-cols-3 gap-3; } + .turbo-setting-grid { @apply mt-3 grid grid-cols-3 gap-2; } + .turbo-setting-grid .number-input { @apply px-2; } + .turbo-setting-grid label { @apply min-h-8; } + .turbo-override { @apply my-3 block; } + .turbo-defaults { @apply mb-4; } + .timing-notice { @apply mb-4 rounded-lg border border-slate-700 bg-slate-850 p-3 text-xs leading-relaxed text-slate-400; } + .timing-notice.warning, .turbo-card.narrow-pulse { @apply border-amber-400/50; } + .timing-notice.warning { @apply text-amber-400; } + .builtin-actions { @apply mb-4; } + .action-card-heading { @apply mb-4; } + .action-card-heading > span { @apply mt-2 block text-xs leading-relaxed text-slate-400; } + .action-kind { @apply text-[11px] font-medium text-blue-500; } + .swing-controls { @apply mb-5; } + .swing-cards { @apply mb-4 flex flex-wrap gap-3; } + .swing-card { @apply min-w-0 flex-[1_1_280px]; } + .swing-card:last-child { @apply basis-full; } + .swing-card-grid { @apply grid gap-3; } + .swing-card:last-child .swing-card-grid { @apply grid-cols-3; } + .macro-controls { @apply mb-5 grid grid-cols-2 gap-4; } + .macro-picker { @apply col-span-full flex flex-wrap items-stretch gap-4 rounded-xl border border-slate-700 bg-slate-950 p-3; } + .macro-tabs { @apply grid flex-[1_1_320px] grid-cols-4 gap-2; } + .macro-tab { @apply rounded-lg border border-slate-700 bg-slate-850 px-3 py-2 text-xs font-medium text-slate-300; } + .macro-tab small { @apply mt-1 block text-[10px]; } + .macro-tab.selected { @apply border-blue-500/60 bg-blue-500/20 text-slate-100; } + .macro-budget { @apply flex flex-[0_1_180px] flex-col justify-center gap-2 text-xs text-slate-400; } + .macro-budget strong { @apply text-slate-100; } + .macro-playback-fields { @apply mt-4 grid grid-cols-2 gap-3; } + .macro-steps-heading, .macro-preview-heading { @apply mb-3 flex flex-wrap items-center justify-between gap-3; } + .macro-step-action, .macro-preview-actions { @apply flex flex-wrap gap-2; } + .macro-steps { @apply grid gap-4; } + .macro-step { @apply min-w-0 rounded-xl border border-slate-700 bg-slate-850 p-4; } + .macro-step.end { @apply border-dashed p-5; } + .step-header { @apply mb-4 flex flex-wrap items-center justify-between gap-3; } + .step-identity, .step-tools { @apply flex flex-wrap items-center gap-2; } + .step-number { @apply text-xs font-semibold text-slate-100; } + .step-timing { @apply text-xs text-slate-400 tabular-nums; } + .step-tool, .remove-step { @apply min-h-9 rounded-md border border-slate-700 bg-slate-950 px-2.5 py-1.5 text-xs text-slate-300; } + .remove-step { @apply border-rose-400/30 text-rose-400; } + .step-handle { @apply cursor-grab active:cursor-grabbing; touch-action: pan-y; } + .step-grid { @apply grid grid-cols-3 gap-3; } + .step-group { @apply mt-4 border-t border-slate-700 pt-4; } + .step-group-title { @apply mb-2 block text-xs font-medium text-slate-300; } + .macro-step.dragging { @apply opacity-40; } + .macro-step.drop-before { box-shadow: 0 -4px 0 var(--color-blue-500); } + .macro-step.drop-after { box-shadow: 0 4px 0 var(--color-blue-500); } + .macro-step.preview-active { @apply border-emerald-400; } + .macro-notice, .capture-notice { @apply my-3 text-xs leading-relaxed text-slate-300; } + .macro-notice:empty, .capture-notice:empty, .capture-events:empty { @apply hidden; } + .macro-notice.error { @apply text-rose-400; } + .macro-preview, .macro-capture { @apply mb-5 rounded-xl border border-slate-700 bg-slate-950/40 p-4; } + .macro-preview[data-state="playing"] { @apply border-emerald-400; } + .macro-capture[data-state="recording"] { @apply border-rose-400; } + .macro-preview > .field-help, .macro-capture > .field-help { @apply my-3; } + .macro-preview-timing { @apply my-3 flex flex-wrap justify-between gap-2 text-xs text-slate-300 tabular-nums; } + .macro-preview-buttons { @apply my-4 rounded-lg border border-slate-700/50 bg-slate-850 p-3; } + .preview-button { @apply inline-flex min-w-14 flex-col gap-1 rounded-md border border-slate-700 px-2 py-1.5 text-xs text-slate-400; } + .preview-button small { @apply text-[10px]; } + .preview-button.pressed { @apply border-emerald-400 bg-emerald-400 text-slate-950; } + .macro-preview [data-mode="override"] { @apply border-emerald-400/70; } + .macro-preview [data-mode="override"] [data-preview-mode] { @apply text-emerald-400; } + .macro-preview [data-mode="passthrough"] .stick-dot-output { visibility: hidden; } + .macro-preview [data-mode="passthrough"] progress { @apply opacity-35; } + .macro-preview [data-preview-value] { @apply mt-2 block text-xs text-slate-400 tabular-nums; } + .macro-preview-triggers { @apply flex flex-col justify-center gap-4; } + .macro-preview-triggers > div { @apply rounded-lg border border-dashed border-slate-700 p-3; } + .capture-options { @apply my-4 min-w-0 rounded-lg border border-slate-700 p-3; } + .capture-options legend { @apply px-1 text-xs text-slate-300; } + .capture-options:disabled { @apply opacity-60; } + .capture-number-fields { @apply mt-4 grid grid-cols-3 gap-3; } + .capture-events { @apply my-3 max-h-64 list-decimal space-y-2 overflow-auto pl-6 text-xs text-slate-400 tabular-nums; } + .capture-events li { @apply rounded border border-slate-700/50 bg-slate-850 p-2; overflow-wrap: anywhere; } + .macro-capture:is([data-state="full"], [data-state="disconnected"], [data-state="error"]) .capture-notice { @apply text-amber-400; } + .loading-card p { @apply max-w-md px-4 text-center leading-relaxed; overflow-wrap: anywhere; } + body:has(.connection[data-state="error"]) .loading-card .spinner { display: none; } + #profileForm [inert] { @apply opacity-50; } + .loading-card { @apply grid min-h-72 place-content-center justify-items-center gap-4 rounded-xl border border-dashed border-slate-700 text-sm text-slate-400; } + .spinner { @apply size-7 animate-spin rounded-full border-2 border-slate-700 border-t-blue-500; } + .toast { @apply pointer-events-none fixed right-5 bottom-5 z-50 max-w-[min(440px,calc(100vw-40px))] translate-y-3 rounded-xl border border-slate-700 bg-slate-850 px-4 py-3 text-sm text-slate-100 opacity-0 shadow-xl transition-all; } + .toast.show { @apply translate-y-0 opacity-100; } + .toast.error { @apply border-rose-400 text-rose-400; } + dialog { @apply fixed inset-0 m-auto max-h-[calc(100dvh-32px)] w-[min(480px,calc(100%-32px))] overflow-y-auto rounded-xl border border-slate-700 bg-slate-900 p-6 text-slate-100 shadow-2xl; } + .adapter-settings-dialog { @apply w-[min(600px,calc(100%-32px))]; } + dialog::backdrop { @apply bg-slate-950/80; } + .dialog-actions { @apply mt-6 flex flex-wrap justify-end gap-2 border-t border-slate-700 pt-4; } + .joycon-mode { @apply mt-5 border-t border-slate-700 pt-5; } +} + +@media (max-width: 1279px) { + .workspace { grid-template-columns: 220px minmax(0, 1fr); gap: 16px; padding-inline: 16px; } + .controller-mapper { grid-template-columns: minmax(0, 1fr); } + .mapping-inspector { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px 16px; } + .mapping-inspector .eyebrow, .mapping-inspector h4, .mapping-inspector > p, .mapping-key { grid-column: 1 / -1; } + .mapping-inspector .field-label { margin-top: 8px; } + .selected-control-mark { display: none; } + .mapping-grid, .turbo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .curve-editor { grid-template-columns: 1fr; } + .playtest-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .trigger-playtest, .macro-preview-triggers { grid-column: 1 / -1; } +} +@media (max-width: 899px) { + .topbar { position: relative; flex-wrap: wrap; padding-block: 16px; gap: 16px; } + .topbar-meta { flex-wrap: wrap; } + .workspace { grid-template-columns: minmax(0, 1fr); } + .sidebar { position: static; max-height: none; } + .profile-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } + .profile-button { flex-wrap: wrap; } + .identity-tools { margin-top: 16px; } + .section-nav { top: 0; } + .panel { scroll-margin-top: 72px; } + .header-actions { align-items: flex-start; } +} +@media (max-width: 599px) { + .topbar { padding-inline: 16px; } + .topbar-meta { width: 100%; justify-content: space-between; gap: 8px; } + .connection { max-width: 48%; } + .workspace { margin-block: 16px; padding-inline: 12px; } + .sidebar, .panel, .editor-header { padding: 16px; } + .profile-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .panel-heading { flex-direction: column; gap: 12px; } + .panel-heading-actions { align-items: flex-start; text-align: left; } + .controller-canvas { padding: 12px; } + .mapping-inspector { display: block; } + .selected-control-mark { display: grid; } + .mapping-grid, .modifier-controls, .analog-grid, .feedback-grid, .builtin-actions, .macro-controls, .turbo-grid, .playtest-grid, .swing-card:last-child .swing-card-grid, .capture-number-fields { grid-template-columns: minmax(0, 1fr); } + .number-grid, .step-grid { grid-template-columns: minmax(0, 1fr); } + .macro-tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .button, .step-tool, .remove-step, .checkbox-pill span { min-height: 44px; } + .subpanel-heading { flex-direction: column; } + .native-joycon-actions .button { flex: 1 1 auto; } + .header-actions { width: 100%; } + .action-group-primary { width: 100%; } + .action-group-primary .button { flex: 1; } +} +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; } +} diff --git a/src/switch_pico_bridge/web/profile_editor.css b/src/switch_pico_bridge/web/profile_editor.css index 35aa0a1..06d3f9b 100644 --- a/src/switch_pico_bridge/web/profile_editor.css +++ b/src/switch_pico_bridge/web/profile_editor.css @@ -1,690 +1,2 @@ -:root { - --canvas: #070913; - --canvas-soft: #0a1020; - --surface: rgba(14, 20, 37, 0.94); - --surface-solid: #0e1425; - --surface-2: #141d32; - --surface-3: #1b2742; - --input: #090f1d; - --ink: #f6f7ff; - --muted: #abb5cc; - --subtle: #7e8aa5; - --line: #303d5b; - --control-line: #60708f; - --line-soft: rgba(129, 147, 190, 0.18); - --blue: #6aa7ff; - --blue-strong: #3d7eea; - --sky: #59c7ff; - --mint: #4adea5; - --amber: #f6c76d; - --rose: #ff7c9f; - --cyan: var(--sky); - --cyan-strong: #34b5f5; - --coral: var(--rose); - --lime: var(--mint); - --warning: var(--amber); - --shadow: 0 28px 80px rgba(1, 3, 12, 0.48); - color-scheme: dark; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -} - -* { box-sizing: border-box; } -html { scroll-behavior: smooth; } -[hidden] { display: none !important; } -body { - margin: 0; - min-width: 320px; - min-height: 100vh; - color: var(--ink); - background: var(--canvas); -} - -button, input, select { font: inherit; } -button, select { cursor: pointer; } -button, .profile-button, .macro-tab, .checkbox-pill span, .select, .number-input { - transition: border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease, color 150ms ease, transform 150ms ease; -} -button:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible { - outline: 3px solid rgba(89, 199, 255, 0.95); - outline-offset: 3px; -} - -.ambient { - position: fixed; - z-index: -1; - width: 30rem; - height: 30rem; - border-radius: 50%; - filter: blur(120px); - opacity: 0.12; - pointer-events: none; -} -.ambient-one { top: 10rem; left: -16rem; background: var(--blue); } -.ambient-two { right: -14rem; bottom: 3rem; background: var(--sky); } - -.topbar { - position: sticky; - top: 0; - z-index: 30; - display: flex; - align-items: center; - justify-content: space-between; - min-height: 84px; - padding: 14px clamp(18px, 4vw, 64px); - border-bottom: 1px solid var(--line-soft); - background: rgba(7, 9, 19, 0.82); - box-shadow: 0 10px 36px rgba(1, 3, 12, 0.24); - backdrop-filter: blur(24px) saturate(140%); -} -.brand { display: flex; align-items: center; gap: 15px; } -.brand-mark { - position: relative; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - width: 44px; - height: 44px; - padding: 8px; - border: 1px solid rgba(106, 167, 255, 0.55); - border-radius: 15px; - background: rgba(106, 167, 255, 0.12); - box-shadow: inset 0 1px rgba(255, 255, 255, 0.08), 0 10px 24px rgba(33, 92, 174, 0.26); - transform: rotate(-3deg); -} -.brand-mark span { border-radius: 8px 5px 5px 8px; background: var(--blue); } -.brand-mark span:last-child { border-radius: 5px 8px 8px 5px; background: var(--sky); } -.topbar-meta { display: flex; align-items: center; gap: 20px; } -.topbar-meta > p { color: var(--subtle); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } -h1, h2, h3, h4, p { margin: 0; } -h1 { font-size: 1.18rem; letter-spacing: -0.025em; } -h2 { font-size: clamp(1.35rem, 2vw, 1.65rem); letter-spacing: -0.04em; } -h3 { font-size: clamp(1.2rem, 1.7vw, 1.42rem); letter-spacing: -0.035em; } -h4 { font-size: 1rem; } -.eyebrow { - margin-bottom: 5px; - color: var(--blue); - font-size: 0.66rem; - font-weight: 850; - letter-spacing: 0.17em; - text-transform: uppercase; -} -.connection { - display: flex; - align-items: center; - gap: 9px; - min-height: 38px; - padding: 8px 13px; - color: var(--muted); - border: 1px solid var(--line); - border-radius: 999px; - background: rgba(16, 23, 41, 0.76); - font-size: 0.78rem; - font-weight: 650; -} -.connection-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--warning); - box-shadow: 0 0 0 4px rgba(246, 199, 109, 0.1), 0 0 14px currentColor; -} -.connection[data-state="ready"] .connection-dot { background: var(--mint); box-shadow: 0 0 0 4px rgba(74, 222, 165, 0.1), 0 0 14px var(--mint); } -.connection[data-state="error"] .connection-dot { background: var(--rose); box-shadow: 0 0 0 4px rgba(255, 124, 159, 0.1), 0 0 14px var(--rose); } - -.workspace { - display: grid; - grid-template-columns: 292px minmax(0, 1fr); - gap: 26px; - width: min(1580px, calc(100% - 40px)); - margin: 28px auto 88px; -} -.sidebar { - position: sticky; - top: 112px; - align-self: start; - max-height: calc(100dvh - 132px); - overflow-y: auto; - scrollbar-gutter: stable; - padding: 22px; - border: 1px solid var(--line); - border-radius: 22px; - background: var(--surface); - box-shadow: var(--shadow), inset 0 1px rgba(255, 255, 255, 0.04); -} -.sidebar-heading, .editor-header, .panel-heading, .subpanel-heading, .title-line { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} -.icon-button { - display: grid; - place-items: center; - width: 40px; - height: 40px; - padding: 0; - color: var(--sky); - border: 1px solid var(--line); - border-radius: 12px; - background: var(--surface-2); - font-size: 1.2rem; -} -.icon-button:hover { border-color: var(--sky); background: rgba(89, 199, 255, 0.1); transform: rotate(20deg); } -.field-label { display: block; margin: 24px 0 8px; color: var(--muted); font-size: 0.69rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.11em; } -.field-help { margin-top: 8px; color: var(--muted); font-size: 0.76rem; line-height: 1.55; } -.select, .number-input { - width: 100%; - min-height: 44px; - padding: 9px 12px; - color: var(--ink); - border: 1px solid var(--control-line); - border-radius: 11px; - background: var(--input); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.28); -} -.select:hover, .number-input:hover { border-color: #7d90b6; } -.select:focus, .number-input:focus { border-color: var(--sky); box-shadow: 0 0 0 4px rgba(89, 199, 255, 0.1); } -.select:disabled, .number-input:disabled { cursor: not-allowed; opacity: 0.45; border-color: var(--line-soft); box-shadow: none; } -.select option:disabled { color: var(--subtle); } -.joycon-mode { margin-top: 22px; padding: 16px 0; border-block: 1px solid var(--line); } -.joycon-mode h3 { font-size: 1rem; } -.joycon-mode .field-label { margin-top: 14px; } -.joycon-mode-help { margin: 12px 0; color: var(--muted); font-size: 0.73rem; line-height: 1.5; } -.joycon-mode-help summary { color: var(--blue); cursor: pointer; } -.joycon-mode-help summary:focus-visible { outline: 3px solid var(--sky); outline-offset: 3px; } -.joycon-mode-help > div { display: grid; gap: 8px; margin-top: 10px; } -.joycon-mode-help strong { color: var(--ink); } -.joycon-mode .button { width: 100%; } -.joycon-mode-status { margin-top: 10px; color: var(--mint); font-size: 0.74rem; line-height: 1.5; overflow-wrap: anywhere; } -.joycon-mode-status[data-state="loading"] { color: var(--muted); } -.joycon-mode-status[data-state="pending"], .joycon-mode-status[data-state="unsupported"] { color: var(--amber); } -.joycon-mode-status[data-state="error"] { color: var(--rose); } -.profile-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; margin-top: 22px; } -.profile-button { - --slot-color: var(--blue); - position: relative; - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - min-height: 66px; - padding: 12px; - overflow: hidden; - color: var(--muted); - border: 1px solid var(--line-soft); - border-radius: 14px; - background: rgba(20, 29, 50, 0.72); - text-align: left; -} -.profile-button::before { position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--slot-color); content: ""; opacity: 0.5; } -.profile-button:nth-child(1) { --slot-color: #59c7ff; } -.profile-button:nth-child(2) { --slot-color: #4adea5; } -.profile-button:nth-child(3) { --slot-color: #f6c76d; } -.profile-button:nth-child(4) { --slot-color: #a58bfa; } -.profile-button:nth-child(5) { --slot-color: #ff7c7c; } -.profile-button:nth-child(6) { --slot-color: #45dfe2; } -.profile-button:nth-child(7) { --slot-color: #ff7cbd; } -.profile-button:nth-child(8) { --slot-color: #c7ed64; } -.profile-button:hover { color: var(--ink); border-color: var(--slot-color); background: var(--surface-2); transform: translateY(-1px); } -.profile-button.selected { color: var(--ink); border-color: var(--slot-color); background: color-mix(in srgb, var(--slot-color) 12%, var(--surface-2)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--slot-color) 12%, transparent); } -.profile-button.selected::before { width: 4px; opacity: 1; } -.profile-number { display: flex; align-items: center; gap: 8px; font-size: 0.74rem; font-weight: 780; } -.profile-number span { display: grid; place-items: center; width: 25px; height: 25px; color: #07101b; border-radius: 8px; background: var(--slot-color); font-size: 0.73rem; font-weight: 900; } -.mini-active { margin: 6px 0 0 33px; color: var(--mint); font-size: 0.59rem; font-weight: 850; letter-spacing: 0.1em; text-transform: uppercase; } -.sidebar-note { display: flex; gap: 11px; margin-top: 22px; padding: 14px 2px 0; color: var(--muted); border-top: 1px solid var(--line-soft); font-size: 0.73rem; line-height: 1.55; } -.note-icon { display: grid; flex: 0 0 auto; place-items: center; width: 22px; height: 22px; color: var(--blue); border: 1px solid rgba(106, 167, 255, 0.55); border-radius: 8px; background: rgba(106, 167, 255, 0.08); font-size: 0.7rem; font-weight: 900; } - -.editor-shell { min-width: 0; } -.editor-header { - min-height: 96px; - margin-bottom: 12px; - padding: 20px 22px; - border: 1px solid var(--line); - border-radius: 20px; - background: var(--surface); - box-shadow: inset 0 1px rgba(255, 255, 255, 0.04); -} -.header-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 9px; } -.button { - min-height: 42px; - padding: 9px 16px; - color: var(--ink); - border: 1px solid var(--control-line); - border-radius: 11px; - background: var(--surface-3); - font-size: 0.8rem; - font-weight: 780; -} -.button:hover { border-color: #596b91; transform: translateY(-1px); } -.button:active { transform: translateY(0); } -.button:disabled { cursor: not-allowed; opacity: 0.42; transform: none; } -.button-primary { color: #080a16; border-color: transparent; background: var(--blue); box-shadow: 0 8px 24px rgba(33, 92, 174, 0.24); } -.button-secondary { border-color: rgba(106, 167, 255, 0.48); background: rgba(106, 167, 255, 0.1); } -.button-ghost { color: var(--muted); background: transparent; } -.active-badge, .dirty-badge { padding: 5px 9px; border: 1px solid currentColor; border-radius: 999px; font-size: 0.62rem; font-weight: 850; letter-spacing: 0.09em; text-transform: uppercase; } -.active-badge { color: var(--mint); background: rgba(74, 222, 165, 0.08); } -.dirty-badge { color: var(--amber); background: rgba(246, 199, 109, 0.08); } -.section-nav { - position: sticky; - top: 96px; - z-index: 18; - display: flex; - gap: 5px; - overflow-x: auto; - margin-bottom: 14px; - padding: 6px; - border: 1px solid var(--line); - border-radius: 14px; - background: rgba(10, 15, 29, 0.9); - box-shadow: 0 12px 30px rgba(1, 3, 12, 0.24); - backdrop-filter: blur(18px); -} -.section-nav a { flex: 1 0 auto; padding: 8px 13px; color: var(--muted); border-radius: 9px; font-size: 0.73rem; font-weight: 780; text-align: center; text-decoration: none; } -.section-nav a:hover, .section-nav a:focus-visible { color: var(--ink); background: var(--surface-3); } - -.panel { - --section-accent: var(--blue); - position: relative; - scroll-margin-top: 152px; - margin-bottom: 20px; - padding: clamp(22px, 3vw, 32px); - overflow: hidden; - border: 1px solid var(--line); - border-radius: 22px; - background: var(--surface); - box-shadow: 0 18px 52px rgba(1, 3, 12, 0.24), inset 0 1px rgba(255, 255, 255, 0.035); -} -.panel::before { position: absolute; inset: 0 auto auto 0; width: 64px; height: 3px; background: var(--section-accent); content: ""; } -#analog { --section-accent: var(--sky); } -#feedback { --section-accent: var(--amber); } -#turbo { --section-accent: var(--mint); } -#macro { --section-accent: var(--rose); } -#shortcuts { --section-accent: var(--sky); } -#shift { --section-accent: var(--amber); } -.panel .eyebrow { color: var(--section-accent); } -.panel-heading { align-items: flex-end; margin-bottom: 26px; } -.panel-heading > p { max-width: 440px; color: var(--muted); font-size: 0.78rem; line-height: 1.55; text-align: right; } -.controller-mapper { display: grid; grid-template-columns: minmax(0, 1fr) 250px; gap: 18px; align-items: stretch; } -.controller-canvas { - min-width: 0; - padding: 18px; - overflow: hidden; - border: 1px solid rgba(89, 199, 255, 0.24); - border-radius: 18px; - background: #090f1d; - box-shadow: inset 0 1px rgba(255, 255, 255, 0.04); -} -.controller-caption { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; min-height: 27px; } -.controller-caption strong { color: #dfe8ff; font-size: 0.8rem; } -.controller-live { display: inline-flex; align-items: center; gap: 7px; color: var(--amber); font-size: 0.65rem; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; } -.controller-live[data-state="live"] { color: var(--mint); } -.layout-picker { margin-block: 16px 8px; } -.layout-picker .select { width: 100%; margin-top: 6px; } -.native-joycon-control { margin-block: 14px 10px; padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: rgba(14, 23, 42, 0.6); } -.native-joycon-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; align-items: center; } -.native-joycon-actions .select { flex: 1 1 220px; } -.native-joycon-actions .button { flex: 0 0 auto; } -.stick-swap-panel { margin-top: 14px; } -.stick-swap-panel .checkbox-pill { display: inline-block; margin-top: 4px; } -.stick-swap-panel .field-help { margin-top: 10px; } -.native-joycon-actions .select { min-width: 0; max-width: 100%; } -.native-joycon-actions .button { min-height: 44px; max-width: 100%; white-space: normal; } -.stick-swap-panel .checkbox-pill span { min-height: 44px; } -.wii-orientation-control { margin-block: 14px 10px; padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: rgba(14, 23, 42, 0.6); } -.wii-orientation-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; align-items: center; } -.wii-orientation-actions .select { flex: 1 1 220px; } -.wii-orientation-actions .button { flex: 0 0 auto; } -.wii-orientation-status { margin-top: 8px; font-size: 0.74rem; line-height: 1.5; overflow-wrap: anywhere; color: var(--mint); } -.wii-orientation-status[data-state="idle"] { display: none; } -.wii-orientation-status[data-state="pending"], .wii-orientation-status[data-state="unsupported"] { color: var(--amber); } -.wii-orientation-status[data-state="error"] { color: var(--rose); } -.wii-orientation-status[data-state="locked"] { color: var(--muted); } -.controller-viewport { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding: 16px 0; scrollbar-color: var(--subtle) var(--surface-2); } -.controller-viewport:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; } -.controller-photo-wrap { --diagram-min: 640px; --diagram-max: 900px; position: relative; width: 100%; min-width: var(--diagram-min); max-width: var(--diagram-max); margin: 0 auto; aspect-ratio: var(--diagram-ratio, 800 / 552); } -.controller-stage { position: absolute; top: 50%; left: 50%; width: var(--stage-width, 100%); height: var(--stage-height, 100%); transform: translate(-50%, -50%) rotate(var(--stage-angle, 0deg)); } -.controller-photo { display: block; width: 100%; height: 100%; pointer-events: none; user-select: none; filter: drop-shadow(0 16px 20px rgba(0, 0, 0, 0.46)); } -.controller-annotation { position: absolute; left: var(--x); top: var(--y); max-width: 170px; padding: 6px; transform: translate(-50%, -50%) rotate(var(--label-angle, 0deg)); border: 1px solid var(--muted); border-radius: 6px; color: var(--ink); background: var(--surface-2); font-size: 0.65rem; text-align: center; pointer-events: none; } -.extra-controls { display: flex; flex-wrap: wrap; gap: 9px; margin: 10px 0; } -.extra-controls button { min-height: 44px; min-width: 48px; padding: 9px 12px; color: var(--ink); border: 1px solid var(--sky); border-radius: 9px; background: var(--surface-2); font-size: 0.72rem; font-weight: 800; cursor: pointer; } -.extra-controls button.disabled-map { color: var(--muted); border-color: var(--control-line); } -.extra-controls button.selected, .extra-controls button:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; } -.extra-controls button.pressed { color: #090f1d; background: var(--amber); border-color: var(--amber); } -.mapping-playtest { margin-top: 14px; font-size: 0.76rem; } -.mapping-playtest > p { margin: 6px 0; } -.controller-hotspots { position: absolute; inset: 0; } -.controller-hotspots button { - --x: 50%; - --y: 50%; - position: absolute; - left: var(--x); - top: var(--y); - display: grid; - place-items: center; - width: 30px; - height: 30px; - padding: 0; - color: #eff6ff; - border: 2px solid var(--sky); - border-radius: 50%; - background: rgba(7, 12, 25, 0.9); - box-shadow: 0 0 0 4px rgba(89, 199, 255, 0.12), 0 5px 16px rgba(0, 0, 0, 0.42); - transform: translate(-50%, -50%) rotate(var(--label-angle, 0deg)); - font-size: 0.59rem; - font-weight: 900; - line-height: 1; - transition: transform 120ms ease, border-color 120ms ease, background 120ms ease, box-shadow 120ms ease; -} -.controller-hotspots button:hover, .controller-hotspots button:focus-visible { z-index: 2; outline: 2px solid var(--blue); outline-offset: 2px; } -.controller-hotspots button.selected { z-index: 3; color: #090a16; border-color: var(--rose); background: var(--rose); box-shadow: 0 0 0 4px rgba(255, 124, 159, 0.18); } -.controller-hotspots button.disabled-map:not(.selected):not(.pressed) { color: var(--subtle); border-color: #56617a; background: rgba(8, 11, 20, 0.9); box-shadow: none; } -.controller-hotspots button[data-controller-button^="dpad_"] { width: 26px; height: 26px; font-size: 0.58rem; } -.source-unavailable { color: var(--amber); } - -.controller-hint { margin-top: 4px; color: var(--muted); font-size: 0.72rem; text-align: center; } -.controller-credit { margin-top: 8px; color: var(--subtle); font-size: 0.64rem; text-align: center; } -.mapping-inspector { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 21px; - border: 1px solid rgba(106, 167, 255, 0.32); - border-radius: 18px; - background: var(--surface-2); -} -.selected-control-mark { display: grid; place-items: center; width: 58px; height: 58px; margin: 15px 0; color: #090a16; border-radius: 18px; background: var(--rose); box-shadow: 0 10px 28px rgba(255, 124, 159, 0.22); font-size: 1.18rem; font-weight: 950; } -.mapping-inspector h4 { font-size: 1.16rem; } -.mapping-inspector > p:not(.eyebrow) { min-height: 50px; margin-top: 8px; color: var(--muted); font-size: 0.76rem; line-height: 1.55; } -.mapping-inspector .field-label { margin-top: 20px; } -.mapping-key { display: flex; flex-wrap: wrap; gap: 13px; margin-top: auto; padding-top: 24px; color: var(--muted); font-size: 0.67rem; } -.mapping-key span { display: inline-flex; align-items: center; gap: 7px; } -.key-dot { width: 9px; height: 9px; border: 2px solid var(--sky); border-radius: 50%; } -.key-dot.disabled { border-color: #69758e; } -.mapping-grid { display: grid; grid-template-columns: repeat(4, minmax(150px, 1fr)); gap: 12px; } -.mapping-grid.compact { grid-template-columns: repeat(4, minmax(140px, 1fr)); } -.control-card { padding: 14px; border: 1px solid var(--line-soft); border-radius: 14px; background: var(--surface-2); } -.control-card:hover { border-color: #415273; } -.control-card label, .number-field label { display: block; margin-bottom: 8px; color: var(--muted); font-size: 0.7rem; font-weight: 760; letter-spacing: 0.035em; } -.control-card .select { min-height: 40px; } -.modifier-controls { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); gap: 14px; margin-bottom: 18px; } -.turbo-defaults { margin-bottom: 14px; } -.turbo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 14px; } -.turbo-setting-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; } -.turbo-setting-grid .number-input { min-width: 0; padding-inline: 9px; } -.turbo-setting-grid .number-field label { min-height: 2.6em; line-height: 1.3; } -.turbo-override { display: block; margin-block: 14px; } -.turbo-card.narrow-pulse { border-color: var(--amber); } -.timing-notice { margin-bottom: 18px; padding: 13px 15px; color: var(--muted); border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); font-size: 0.76rem; line-height: 1.6; } -.timing-notice.warning { color: var(--amber); border-color: var(--amber); } -.macro-playback-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 20px; } -.playtest-panel { margin-bottom: 18px; padding: 19px; border: 1px solid rgba(89, 199, 255, 0.28); border-radius: 17px; background: #0b1324; } -.playtest-panel[data-state="waiting"] .playtest-grid { opacity: 0.48; } -.playtest-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; } -.playtest-heading h4 { margin-top: 3px; } -.playtest-status { padding: 5px 9px; color: var(--amber); border: 1px solid currentColor; border-radius: 999px; font-size: 0.62rem; font-weight: 850; letter-spacing: 0.08em; text-transform: uppercase; } -.playtest-panel[data-state="live"] .playtest-status { color: var(--mint); } -.playtest-panel[data-state="error"] .playtest-status { color: var(--rose); } -.playtest-help { margin: 7px 0 16px; color: var(--muted); font-size: 0.73rem; } -.playtest-grid { display: grid; grid-template-columns: repeat(2, minmax(180px, 1fr)) minmax(300px, 1.4fr); gap: 12px; transition: opacity 150ms ease; } -.playtest-card { min-width: 0; padding: 14px; border: 1px solid var(--line-soft); border-radius: 14px; background: var(--surface-2); } -.playtest-card-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 11px; } -.playtest-card-heading strong { font-size: 0.78rem; } -.playtest-card-heading span { color: var(--muted); font-size: 0.63rem; font-variant-numeric: tabular-nums; } -.stick-scope { --raw-x: 50%; --raw-y: 50%; --output-x: 50%; --output-y: 50%; --inner-size: 0%; --outer-size: 100%; position: relative; width: min(100%, 174px); margin: auto; aspect-ratio: 1; overflow: hidden; border: 1px solid var(--control-line); border-radius: 50%; background: #080e1c; } -.stick-axis { position: absolute; background: rgba(171, 181, 204, 0.16); } -.stick-axis-x { top: 50%; right: 10%; left: 10%; height: 1px; } -.stick-axis-y { top: 10%; bottom: 10%; left: 50%; width: 1px; } -.stick-ring { position: absolute; top: 50%; left: 50%; border: 1px solid; border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; } -.stick-ring-outer { width: var(--outer-size); height: var(--outer-size); border-color: rgba(89, 199, 255, 0.62); } -.stick-ring-inner { width: var(--inner-size); height: var(--inner-size); border-color: rgba(255, 124, 159, 0.8); background: rgba(255, 124, 159, 0.05); } -.stick-dot { position: absolute; top: 50%; left: 50%; border-radius: 50%; transform: translate(-50%, -50%); transition: left 45ms linear, top 45ms linear; } -.stick-dot-raw { top: var(--raw-y); left: var(--raw-x); z-index: 2; width: 15px; height: 15px; border: 3px solid var(--amber); background: transparent; box-shadow: 0 0 0 2px rgba(246, 199, 109, 0.12); } -.stick-dot-output { top: var(--output-y); left: var(--output-x); z-index: 3; width: 8px; height: 8px; border: 2px solid #080e1c; background: var(--blue); box-shadow: 0 0 0 2px rgba(106, 167, 255, 0.3); } -.trigger-playtest { display: flex; flex-direction: column; } -.trigger-meter { display: grid; grid-template-columns: 28px minmax(120px, 1fr) 88px; align-items: center; gap: 9px; margin-top: 12px; } -.trigger-meter > span { color: var(--ink); font-size: 0.7rem; font-weight: 850; } -.trigger-meter output { color: var(--muted); font-size: 0.65rem; font-variant-numeric: tabular-nums; text-align: right; } -.trigger-track { --raw: 0%; --output: 0%; --threshold: 35%; position: relative; height: 28px; overflow: hidden; border: 1px solid var(--control-line); border-radius: 8px; background: #080e1c; } -.trigger-bar { position: absolute; left: 0; height: 7px; border-radius: 0 4px 4px 0; transition: width 45ms linear; } -.trigger-bar-raw { top: 4px; width: var(--raw); background: var(--amber); } -.trigger-bar-output { bottom: 4px; width: var(--output); background: var(--blue); } -.trigger-threshold { position: absolute; top: 0; bottom: 0; left: var(--threshold); width: 2px; background: var(--rose); transform: translateX(-1px); } -.playtest-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: auto; padding-top: 16px; color: var(--muted); font-size: 0.61rem; } -.playtest-legend span { display: inline-flex; align-items: center; gap: 5px; } -.playtest-legend i { width: 8px; height: 8px; border-radius: 50%; } -.legend-raw { background: var(--amber); } -.legend-output { background: var(--blue); } -.legend-threshold { background: var(--rose); } -.controller-hotspots button.pressed { z-index: 4; color: #071018; border-color: var(--mint); background: var(--mint); box-shadow: 0 0 0 5px rgba(74, 222, 165, 0.2), 0 8px 20px rgba(0, 0, 0, 0.42); } -.analog-grid, .feedback-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } -.subpanel { padding: 19px; border: 1px solid var(--line-soft); border-radius: 16px; background: var(--surface-2); } -.subpanel-heading { align-items: flex-start; margin-bottom: 16px; } -.subpanel-heading span { max-width: 240px; color: var(--muted); font-size: 0.72rem; line-height: 1.45; text-align: right; } -.number-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } -.number-field.wide { grid-column: 1 / -1; } -.toggle-row { display: flex; flex-wrap: wrap; gap: 8px; grid-column: 1 / -1; } -.checkbox-pill { position: relative; } -.checkbox-pill input { position: absolute; opacity: 0; pointer-events: none; } -.checkbox-pill span { display: block; padding: 8px 11px; color: var(--muted); border: 1px solid var(--control-line); border-radius: 999px; background: rgba(9, 15, 29, 0.45); font-size: 0.7rem; font-weight: 750; cursor: pointer; } -.checkbox-pill span:hover { color: var(--ink); border-color: #7d90b6; background: var(--surface-3); } -.checkbox-pill input:focus-visible + span { outline: 3px solid rgba(89, 199, 255, 0.95); outline-offset: 3px; } -.checkbox-pill input:checked + span { color: #080b15; border-color: var(--blue); background: var(--blue); box-shadow: 0 6px 18px rgba(33, 92, 174, 0.2); } -.checkbox-pill input:disabled + span { opacity: 0.4; cursor: not-allowed; } -.controller-choices { align-items: stretch; gap: 8px; } -.controller-choice span { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-width: 60px; - min-height: 58px; - padding: 8px 9px; - border-radius: 14px; - text-align: center; -} -.controller-choice span[data-button$="_shoulder"], -.controller-choice span[data-button$="_trigger"], -.controller-choice span[data-button$="_stick"] { min-width: 78px; } -.controller-choice b { color: var(--ink); font-size: 1.05rem; font-weight: 930; line-height: 1; } -.controller-choice small { max-width: 78px; margin-top: 6px; color: var(--muted); font-size: 0.59rem; font-weight: 720; line-height: 1.1; } -.controller-choice input:checked + span b, -.controller-choice input:checked + span small { color: #080b15; } -.builtin-actions[data-controller-style="playstation"] .controller-choice input:not(:checked) + span[data-button="north"] b { color: #6fdda9; } -.builtin-actions[data-controller-style="playstation"] .controller-choice input:not(:checked) + span[data-button="east"] b { color: #ff8995; } -.builtin-actions[data-controller-style="playstation"] .controller-choice input:not(:checked) + span[data-button="south"] b { color: #7fbcff; } -.builtin-actions[data-controller-style="playstation"] .controller-choice input:not(:checked) + span[data-button="west"] b { color: #dda1ff; } -.check-grid { display: flex; flex-wrap: wrap; gap: 8px; } -.range-row { margin-bottom: 18px; } -.range-meta { display: flex; justify-content: space-between; margin-bottom: 9px; color: var(--muted); font-size: 0.74rem; } -.range-meta output { color: var(--ink); font-weight: 760; font-variant-numeric: tabular-nums; } -input[type="range"] { width: 100%; accent-color: var(--blue); } -.builtin-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 18px; } -.action-card { padding: 19px; border: 1px solid var(--line-soft); border-radius: 17px; background: var(--surface-2); } -.action-card-heading { min-height: 76px; margin-bottom: 14px; } -.action-card-heading h4 { margin-top: 5px; font-size: 1.04rem; } -.action-card-heading > span { display: block; margin-top: 8px; color: var(--muted); font-size: 0.73rem; line-height: 1.45; } -.action-kind { color: var(--section-accent); font-size: 0.62rem; font-weight: 850; letter-spacing: 0.11em; text-transform: uppercase; } - -.swing-controls { margin-bottom: 18px; } -.swing-cards { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 14px; } -.swing-card { display: flex; flex-direction: column; flex: 1 1 320px; min-width: 0; } -.swing-card .action-card-heading { min-height: 0; margin-bottom: 12px; } -.swing-card-grid { display: grid; grid-template-columns: 1fr; gap: 12px; } -.swing-card:last-child { flex-basis: 100%; } -.swing-card:last-child .swing-card-grid { grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); } -.macro-controls { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(280px, 0.9fr); gap: 14px; margin-bottom: 20px; } -.macro-picker { display: flex; grid-column: 1 / -1; align-items: stretch; justify-content: space-between; gap: 14px; padding: 13px; border: 1px solid var(--line); border-radius: 16px; background: #090f1d; } -.macro-tabs { display: grid; grid-template-columns: repeat(4, minmax(90px, 1fr)); gap: 8px; flex: 1; } -.macro-tab { padding: 10px 12px; color: var(--muted); border: 1px solid var(--control-line); border-radius: 11px; background: var(--surface-2); font-weight: 820; } -.macro-tab small { display: block; margin-top: 4px; font-size: 0.61rem; font-weight: 650; } -.macro-tab.selected { color: #080a15; border-color: transparent; background: var(--rose); box-shadow: 0 7px 20px rgba(255, 124, 159, 0.2); } -.macro-budget { display: flex; flex: 0 0 180px; flex-direction: column; justify-content: center; color: var(--muted); font-size: 0.7rem; } -.macro-budget strong { color: var(--ink); } -.macro-budget progress { width: 100%; height: 6px; margin-top: 9px; overflow: hidden; border: 0; border-radius: 999px; background: var(--surface-3); accent-color: var(--rose); } -.macro-budget progress::-webkit-progress-bar { border-radius: inherit; background: var(--surface-3); } -.macro-budget progress::-webkit-progress-value { border-radius: inherit; background: var(--rose); } -.macro-steps-heading { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin: 6px 0 13px; padding: 0 4px; } -.macro-steps-heading h4 { margin-top: 4px; font-size: 1.05rem; } -.macro-step-action { display: flex; } -.macro-steps { display: grid; gap: 13px; } -.macro-step { padding: 18px; border: 1px solid var(--line); border-left: 3px solid var(--rose); border-radius: 15px; background: var(--surface-2); } -.macro-step.end { min-height: 68px; border: 1px dashed #43506c; border-left: 3px solid var(--subtle); background: rgba(20, 29, 50, 0.46); opacity: 1; } -.macro-step.end .field-help { color: var(--muted); } -.step-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 15px; } -.step-number { color: var(--rose); font-size: 0.7rem; font-weight: 850; letter-spacing: 0.12em; text-transform: uppercase; } -.remove-step { min-height: 32px; padding: 6px 10px; color: #ff9ab2; border: 1px solid rgba(255, 124, 159, 0.25); border-radius: 8px; background: rgba(255, 124, 159, 0.08); font-size: 0.7rem; font-weight: 720; } -.remove-step:hover { border-color: var(--rose); background: rgba(255, 124, 159, 0.14); } -.step-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; } -.step-group { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line-soft); } -.step-group-title { display: block; margin-bottom: 9px; color: var(--muted); font-size: 0.69rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; } -.step-identity, .step-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; } -.step-timing { color: var(--muted); font-size: 0.7rem; font-variant-numeric: tabular-nums; } -.step-tool { min-height: 34px; padding: 6px 9px; color: var(--muted); border: 1px solid var(--control-line); border-radius: 8px; background: var(--input); font-size: 0.7rem; font-weight: 720; } -.step-tool:hover:not(:disabled) { color: var(--ink); border-color: var(--sky); } -.step-handle { cursor: grab; touch-action: pan-y; } -.step-handle:active { cursor: grabbing; } -.step-tool:disabled, .remove-step:disabled { cursor: not-allowed; opacity: 0.4; } -.macro-step.dragging { opacity: 0.5; } -.macro-step.drop-before { box-shadow: 0 -4px 0 var(--sky); } -.macro-step.drop-after { box-shadow: 0 4px 0 var(--sky); } -.macro-step.preview-active { border-color: var(--mint); box-shadow: 0 0 0 1px var(--mint); } -.macro-notice { min-height: 1.4em; margin: 9px 0 14px; color: var(--mint); font-size: 0.76rem; line-height: 1.5; } -.macro-notice.error { color: var(--rose); } -.macro-preview { margin-bottom: 24px; padding: 19px; border: 1px solid var(--line); border-radius: 17px; background: #0b1324; } -.macro-preview[data-state="playing"] { border-color: var(--mint); } -.macro-preview-heading { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 14px; } -.macro-preview-heading h4 { margin-top: 5px; } -.macro-preview-actions { display: flex; flex-wrap: wrap; gap: 6px; } -.macro-preview > .field-help { margin-block: 12px; } -.macro-preview-timing { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; margin-block: 10px; color: var(--muted); font-size: 0.73rem; } -.macro-preview-timing output { font-variant-numeric: tabular-nums; } -.macro-preview progress { width: 100%; height: 8px; border: 0; border-radius: 999px; background: var(--surface-3); accent-color: var(--mint); } -.macro-preview progress::-webkit-progress-bar { border-radius: 999px; background: var(--surface-3); } -.macro-preview progress::-webkit-progress-value { border-radius: 999px; background: var(--mint); } -.macro-preview-buttons { margin-block: 14px; padding: 14px; border: 1px solid var(--line-soft); border-radius: 14px; background: var(--surface-2); } -.preview-button { display: inline-flex; flex-direction: column; gap: 3px; min-width: 64px; padding: 7px 9px; color: var(--muted); border: 1px solid var(--line); border-radius: 8px; font-size: 0.7rem; } -.preview-button small { font-size: 0.58rem; font-weight: 600; } -.preview-button.pressed { color: #080e1c; border-color: var(--mint); background: var(--mint); } -.macro-preview [data-mode="override"] { border-color: var(--mint); } -.macro-preview [data-mode="override"] [data-preview-mode] { color: var(--mint); } -.macro-preview [data-mode="passthrough"] .stick-dot-output { visibility: hidden; } -.macro-preview [data-mode="passthrough"] progress { opacity: 0.35; } -.macro-preview [data-preview-value] { display: block; margin-top: 9px; color: var(--muted); font-size: 0.7rem; font-variant-numeric: tabular-nums; } -.macro-preview-triggers { display: flex; flex-direction: column; justify-content: center; gap: 16px; } -.macro-preview-triggers > div { padding: 10px; border: 1px dashed var(--line); border-radius: 9px; } -.macro-capture { margin-bottom: 24px; padding: 19px; border: 1px solid rgba(255, 124, 159, 0.35); border-radius: 17px; background: #141323; } -.macro-capture[data-state="recording"] { border-color: var(--rose); box-shadow: 0 0 0 1px rgba(255, 124, 159, 0.22); } -.macro-capture > .field-help { margin-block: 12px; } -.capture-options { min-width: 0; margin-block: 18px; padding: 14px; border: 1px solid var(--line); border-radius: 12px; } -.capture-options legend { padding-inline: 6px; color: var(--muted); font-size: 0.73rem; font-weight: 700; } -.capture-options:disabled { opacity: 0.65; } -.capture-number-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-top: 16px; } -.macro-capture > progress { width: 100%; height: 8px; accent-color: var(--rose); } -.capture-notice { min-height: 1.5em; margin-block: 12px; color: var(--muted); font-size: 0.76rem; line-height: 1.55; } -.macro-capture:is([data-state="full"], [data-state="disconnected"], [data-state="error"]) .capture-notice { color: var(--amber); } -.capture-events { display: grid; gap: 7px; max-height: 260px; margin: 14px 0; padding-left: 28px; overflow: auto; color: var(--muted); font-size: 0.72rem; font-variant-numeric: tabular-nums; } -.capture-events:empty { display: none; } -.capture-events li { padding: 9px; border: 1px solid var(--line-soft); border-radius: 8px; background: var(--surface-2); overflow-wrap: anywhere; } -.capture-events strong { color: var(--ink); } -#profileForm [inert] { opacity: 0.55; } -.loading-card { display: grid; place-items: center; min-height: 300px; color: var(--muted); border: 1px dashed var(--line); border-radius: 22px; background: var(--surface); } -.spinner { width: 30px; height: 30px; margin-bottom: -90px; border: 3px solid var(--surface-3); border-top-color: var(--blue); border-radius: 50%; animation: spin 0.8s linear infinite; } -@keyframes spin { to { transform: rotate(360deg); } } -.toast { position: fixed; right: 24px; bottom: 24px; z-index: 40; max-width: min(430px, calc(100vw - 48px)); padding: 14px 17px; color: var(--ink); border: 1px solid var(--line); border-radius: 13px; background: #172139; box-shadow: var(--shadow); opacity: 0; transform: translateY(12px); pointer-events: none; transition: 180ms ease; } -.toast.show { opacity: 1; transform: translateY(0); } -.toast.error { border-color: var(--rose); } - -.identity-tools .field-label { margin-top: 18px; } -.identity-actions { display: flex; gap: 8px; margin-top: 9px; } -.identity-actions .button { flex: 1; padding-inline: 8px; } -.controller-details { min-height: 18px; margin-top: 8px; color: var(--subtle); font-size: 0.67rem; } -.profile-name-editor { display: grid; grid-template-columns: minmax(140px, 240px) auto; align-items: end; gap: 8px; margin-top: 12px; } -.profile-name-editor label { grid-column: 1 / -1; color: var(--muted); font-size: 0.66rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase; } -.profile-name-editor input { min-height: 40px; padding: 8px 11px; color: var(--ink); border: 1px solid var(--control-line); border-radius: 10px; background: var(--input); } -.panel-heading-actions { display: flex; align-items: flex-end; flex-direction: column; gap: 8px; } -.panel-heading-actions p { max-width: 440px; color: var(--muted); font-size: 0.78rem; line-height: 1.55; text-align: right; } -.section-reset { padding: 0; color: var(--sky); border: 0; background: transparent; font-size: 0.68rem; font-weight: 760; } -.section-reset:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; } -.curve-editor { display: grid; grid-template-columns: minmax(120px, 0.8fr) minmax(150px, 1.2fr); gap: 14px; margin-bottom: 16px; padding: 12px; border: 1px solid var(--line-soft); border-radius: 13px; background: #0a1120; } -.curve-editor svg { width: 100%; min-height: 110px; overflow: visible; border-left: 1px solid var(--line); border-bottom: 1px solid var(--line); } -.curve-editor path { fill: none; vector-effect: non-scaling-stroke; } -.curve-guide { stroke: var(--subtle); stroke-dasharray: 3 3; opacity: 0.45; } -.curve-line { stroke: var(--blue); stroke-width: 2.5; } -.curve-marker { fill: var(--amber); stroke: #0a1120; stroke-width: 2; opacity: 0; transition: opacity 120ms ease; } -.curve-marker.visible { opacity: 1; } -.curve-controls { display: flex; flex-direction: column; gap: 10px; } -.curve-controls label { color: var(--muted); font-size: 0.67rem; font-weight: 720; } -.curve-controls output { float: right; color: var(--ink); } -.curve-controls input[type="range"] { display: block; margin-top: 8px; } -.copy-side { min-height: 34px; margin-top: auto; padding-block: 6px; } -.copy-dialog, .adapter-settings-dialog { width: min(440px, calc(100% - 28px)); padding: 24px; color: var(--ink); border: 1px solid var(--line); border-radius: 18px; background: var(--surface-solid); box-shadow: var(--shadow); } -.copy-dialog::backdrop, .adapter-settings-dialog::backdrop { background: rgba(2, 5, 13, 0.76); } -.copy-dialog h3 { margin-bottom: 18px; } -.dialog-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 22px; } -.adapter-settings-dialog { width: min(580px, calc(100% - 28px)); max-height: calc(100dvh - 28px); overflow-y: auto; } -.adapter-settings-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; } -.adapter-settings-heading .button { flex: 0 0 auto; min-height: 44px; } -.adapter-settings-dialog .joycon-mode { margin-top: 20px; padding-bottom: 0; border-bottom: 0; } -@media (max-width: 1120px) { - .workspace { grid-template-columns: 250px minmax(0, 1fr); } - .profile-list { grid-template-columns: 1fr; } - .mapping-grid, .mapping-grid.compact { grid-template-columns: repeat(2, minmax(140px, 1fr)); } - .controller-mapper { grid-template-columns: 1fr; } - .playtest-grid { grid-template-columns: repeat(2, minmax(160px, 1fr)); } - .trigger-playtest { grid-column: 1 / -1; } - .mapping-inspector { min-height: 270px; } - .editor-header { align-items: flex-start; } -} -@media (max-width: 780px) { - .topbar { position: relative; min-height: 76px; flex-wrap: wrap; gap: 12px; } - .topbar-meta > p { display: none; } - .topbar-meta { width: 100%; justify-content: space-between; gap: 12px; } - .topbar-meta .adapter-settings-button { flex: 0 0 auto; min-height: 44px; } - .connection { max-width: 52%; } - .workspace { display: block; width: min(100% - 24px, 700px); margin-top: 14px; } - .sidebar { position: relative; top: auto; margin-bottom: 18px; max-height: none; overflow: visible; scrollbar-gutter: auto; } - .profile-list { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .profile-button { min-height: 62px; } - .profile-number span { font-size: 0.74rem; } - .mini-active { margin-left: 0; } - .editor-header, .panel-heading { align-items: flex-start; flex-direction: column; } - .header-actions { justify-content: flex-start; } - .curve-editor { grid-template-columns: 1fr; } - .section-nav { top: 8px; } - .builtin-actions, .analog-grid, .feedback-grid, .macro-controls { grid-template-columns: 1fr; } - .playtest-grid { grid-template-columns: 1fr; } - .trigger-playtest { grid-column: auto; } - .panel-heading > p, .subpanel-heading span { text-align: left; } - .macro-picker { flex-direction: column; } - .macro-steps-heading { align-items: stretch; flex-direction: column; } - .macro-step-action .button { width: 100%; } - .macro-budget { flex-basis: auto; } - .step-grid { grid-template-columns: 1fr 1fr; } - .macro-preview .playtest-grid { grid-template-columns: 1fr; } - .macro-preview-heading, .macro-preview-actions { align-items: stretch; } - .macro-preview-actions .button, .step-tool, .remove-step { min-height: 44px; } -} -@media (max-width: 500px) { - .topbar { padding-inline: 14px; } - .brand-mark { width: 38px; height: 38px; } - .connection { max-width: 48%; padding: 7px 9px; font-size: 0.68rem; } - .workspace { width: min(100% - 16px, 480px); } - .sidebar, .panel, .editor-header { border-radius: 17px; } - .profile-name-editor { grid-template-columns: 1fr; } - .panel-heading-actions { align-items: flex-start; } - .panel-heading-actions p { text-align: left; } - .sidebar, .panel { padding: 18px; } - .trigger-meter { grid-template-columns: 25px minmax(100px, 1fr); } - .trigger-meter output { grid-column: 2; text-align: left; } - .profile-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .mapping-grid, .mapping-grid.compact, .number-grid, .step-grid { grid-template-columns: 1fr; } - .button { flex: 1 1 auto; min-height: 44px; } - .header-actions { width: 100%; } - .section-nav a { padding-inline: 11px; } - .macro-tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .controller-canvas { padding: 12px; } -} -@media (prefers-reduced-motion: reduce) { - html { scroll-behavior: auto; } - *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; } -} +/*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-amber-400:#ebc174;--color-emerald-400:#6ed4b1;--color-sky-200:oklch(90.1% .058 230.902);--color-sky-500:#80afff;--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:#80afff;--color-rose-400:#f19aaa;--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-700:#3c4c63;--color-slate-800:#202c3e;--color-slate-900:#121a27;--color-slate-950:#0b1019;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-slate-850:#17202e}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{color-scheme:dark}body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}[hidden]{display:none!important}h2,h3,h4{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-slate-100)}h2,h3{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}button,select,summary{cursor:pointer}button:disabled,select:disabled,input:disabled{cursor:not-allowed;opacity:.45}:is(button,input,select,a,summary,[tabindex]):focus-visible{outline:2px solid var(--color-blue-500);outline-offset:3px}input[type=range],progress{accent-color:var(--color-blue-500)}input[type=range]{width:100%}input,select,fieldset{min-width:0}progress{height:calc(var(--spacing) * 2);border-radius:3.40282e38px;width:100%;overflow:hidden}progress::-webkit-progress-bar{background-color:var(--color-slate-800);border-radius:3.40282e38px}progress::-webkit-progress-value{background-color:var(--color-blue-500);border-radius:3.40282e38px}*{scrollbar-width:thin;scrollbar-color:var(--color-slate-700) transparent}}@layer components{.topbar{z-index:30;min-height:calc(var(--spacing) * 20);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-800);background-color:var(--color-slate-950);padding-inline:calc(var(--spacing) * 6);display:flex;position:sticky;top:0}.brand{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.brand-mark{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9);gap:var(--spacing);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:#80afff4d;flex-shrink:0;grid-template-columns:repeat(2,minmax(0,1fr));display:grid}@supports (color:color-mix(in lab, red, red)){.brand-mark{border-color:color-mix(in oklab, var(--color-blue-500) 30%, transparent)}}.brand-mark{background-color:#80afff1a}@supports (color:color-mix(in lab, red, red)){.brand-mark{background-color:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.brand-mark{padding:calc(var(--spacing) * 2)}.brand-mark span{border-radius:var(--radius-sm);background-color:var(--color-blue-500)}.brand-mark span:last-child{background-color:#80afff80}@supports (color:color-mix(in lab, red, red)){.brand-mark span:last-child{background-color:color-mix(in oklab, var(--color-blue-500) 50%, transparent)}}.topbar-meta{align-items:center;gap:calc(var(--spacing) * 4);display:flex}.connection{align-items:center;gap:calc(var(--spacing) * 2);min-width:0;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300);display:flex}.connection-dot{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);background-color:var(--color-amber-400);border-radius:3.40282e38px;flex-shrink:0}.connection[data-state=ready] .connection-dot{background-color:var(--color-emerald-400)}.connection[data-state=error] .connection-dot{background-color:var(--color-rose-400)}.workspace{margin-inline:auto;margin-block:calc(var(--spacing) * 6);align-items:flex-start;gap:calc(var(--spacing) * 6);width:100%;max-width:1720px;padding-inline:calc(var(--spacing) * 6);grid-template-columns:248px minmax(0,1fr);display:grid}.sidebar{top:calc(var(--spacing) * 26);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-800);background-color:var(--color-slate-900);max-height:calc(100dvh - 120px);padding:calc(var(--spacing) * 5);position:sticky;overflow-y:auto}.sidebar-heading,.adapter-settings-heading{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.eyebrow{margin-bottom:var(--spacing);--tw-font-weight:var(--font-weight-semibold);font-size:11px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-slate-400);text-transform:uppercase}.icon-button{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);color:var(--color-slate-300);flex-shrink:0;justify-content:center;align-items:center;display:flex}@media (hover:hover){.icon-button:hover{background-color:var(--color-slate-800)}}.field-label{margin-top:calc(var(--spacing) * 5);margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-300);display:block}.field-help,.help-details p{margin-top:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.identity-actions{margin-top:calc(var(--spacing) * 2);gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.identity-actions .button{padding-inline:calc(var(--spacing) * 2);flex:1}.controller-details{margin-top:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400);overflow-wrap:anywhere}.sidebar-note{margin-top:calc(var(--spacing) * 5);gap:calc(var(--spacing) * 2);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-800);padding-top:calc(var(--spacing) * 4);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400);display:flex}.note-icon{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);border-radius:3.40282e38px;flex-shrink:0;justify-content:center;align-items:center;font-size:10px;display:flex}.profile-list{gap:calc(var(--spacing) * 1.5);display:grid}.profile-button{min-height:calc(var(--spacing) * 11);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-left-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:left;color:var(--color-slate-300);border-color:#0000;border-left-width:3px;display:flex}@media (hover:hover){.profile-button:hover{background-color:var(--color-slate-800)}}.profile-button{border-left-color:var(--profile-color)}.profile-button[data-profile-index="0"]{--profile-color:#05f}.profile-button[data-profile-index="1"]{--profile-color:#0c6}.profile-button[data-profile-index="2"]{--profile-color:#fa0}.profile-button[data-profile-index="3"]{--profile-color:#c3f}.profile-button[data-profile-index="4"]{--profile-color:#f44}.profile-button[data-profile-index="5"]{--profile-color:#0dd}.profile-button[data-profile-index="6"]{--profile-color:#f6b}.profile-button[data-profile-index="7"]{--profile-color:#cf3}.profile-button.selected{color:var(--color-slate-100);border-color:var(--profile-color);background:var(--profile-color)}@supports (color:color-mix(in lab, red, red)){.profile-button.selected{background:color-mix(in srgb, var(--profile-color) 12%, var(--color-slate-900))}}.profile-number{align-items:center;gap:calc(var(--spacing) * 3);min-width:0;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);overflow-wrap:anywhere;display:flex}.profile-number span{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-950);background:var(--profile-color);border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;display:flex}.profile-button[data-profile-index="0"] .profile-number span{color:var(--color-white)}.mini-active{--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);color:var(--color-emerald-400);flex-shrink:0}.editor-shell{min-width:0}.editor-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 5);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-800);background-color:var(--color-slate-900);padding:calc(var(--spacing) * 5);flex-wrap:wrap;display:flex}.title-line{flex-wrap:wrap}.profile-name-editor{margin-top:calc(var(--spacing) * 3);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.profile-name-editor input{height:calc(var(--spacing) * 10);width:100%;max-width:calc(var(--spacing) * 60);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.header-actions{align-items:flex-end;gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.action-group-primary{order:-1}.active-badge,.dirty-badge{border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-size:11px;font-weight:var(--font-weight-medium);border-width:1px;display:inline-flex}.active-badge{border-color:#6ed4b133}@supports (color:color-mix(in lab, red, red)){.active-badge{border-color:color-mix(in oklab, var(--color-emerald-400) 20%, transparent)}}.active-badge{background-color:#6ed4b11a}@supports (color:color-mix(in lab, red, red)){.active-badge{background-color:color-mix(in oklab, var(--color-emerald-400) 10%, transparent)}}.active-badge{color:var(--color-emerald-400)}.dirty-badge{border-color:#ebc17433}@supports (color:color-mix(in lab, red, red)){.dirty-badge{border-color:color-mix(in oklab, var(--color-amber-400) 20%, transparent)}}.dirty-badge{background-color:#ebc1741a}@supports (color:color-mix(in lab, red, red)){.dirty-badge{background-color:color-mix(in oklab, var(--color-amber-400) 10%, transparent)}}.dirty-badge{color:var(--color-amber-400)}.button{min-height:calc(var(--spacing) * 10);justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-width:1px;border-color:#0000;display:inline-flex}.button-primary{border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-slate-950)}@media (hover:hover){.button-primary:hover{background-color:var(--color-blue-400)}}.button-secondary{border-color:var(--color-slate-700);background-color:#202c3e80}@supports (color:color-mix(in lab, red, red)){.button-secondary{background-color:color-mix(in oklab, var(--color-slate-800) 50%, transparent)}}.button-secondary{color:var(--color-slate-200)}@media (hover:hover){.button-secondary:hover{background-color:var(--color-slate-800)}}.button-ghost{color:var(--color-slate-400)}@media (hover:hover){.button-ghost:hover{background-color:var(--color-slate-800);color:var(--color-slate-100)}}.select,.number-input{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);width:100%;min-width:0;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-slate-100)}.section-nav{top:calc(var(--spacing) * 20);z-index:20;margin-bottom:calc(var(--spacing) * 4);gap:var(--spacing);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding-block:calc(var(--spacing) * 2);display:flex;position:sticky;overflow-x:auto}.section-nav a{border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-400);flex-shrink:0}@media (hover:hover){.section-nav a:hover{background-color:var(--color-slate-800);color:var(--color-slate-100)}}.section-nav a[aria-current=page]{background-color:#80afff26}@supports (color:color-mix(in lab, red, red)){.section-nav a[aria-current=page]{background-color:color-mix(in oklab, var(--color-blue-500) 15%, transparent)}}.section-nav a[aria-current=page]{color:var(--color-blue-500)}.panel{margin-bottom:calc(var(--spacing) * 6);min-width:0;scroll-margin-top:calc(var(--spacing) * 40);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-800);background-color:var(--color-slate-900);padding:calc(var(--spacing) * 6)}.panel-heading{margin-bottom:calc(var(--spacing) * 6);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 5);display:flex}.panel-heading-actions{max-width:var(--container-sm);align-items:flex-end;gap:calc(var(--spacing) * 2);text-align:right;flex-direction:column;display:flex}.panel-heading-actions p{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.section-reset{min-height:calc(var(--spacing) * 8);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-500)}@media (hover:hover){.section-reset:hover{text-decoration-line:underline}}.help-details{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-800);padding-top:calc(var(--spacing) * 3)}.help-details summary,.joycon-mode-help summary{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-500)}.help-details strong{color:var(--color-slate-200)}.controller-mapper{align-items:flex-start;gap:calc(var(--spacing) * 4);grid-template-columns:minmax(0,1fr) 240px;display:grid}.controller-canvas{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-800);background-color:var(--color-slate-950);min-width:0;padding:calc(var(--spacing) * 4);overflow:hidden}.controller-caption{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.controller-caption strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.controller-live{color:var(--color-amber-400);font-size:11px}.controller-live[data-state=live]{color:var(--color-emerald-400)}.controller-viewport{max-width:100%;padding-block:calc(var(--spacing) * 4);overflow:auto hidden}.controller-photo-wrap{width:100%;min-width:var(--diagram-min,640px);max-width:var(--diagram-max,900px);aspect-ratio:var(--diagram-ratio,800 / 552);margin:auto;position:relative}.controller-stage{width:var(--stage-width,100%);height:var(--stage-height,100%);transform:translate(-50%, -50%) rotate(var(--stage-angle,0deg));position:absolute;top:50%;left:50%}.controller-photo{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;display:block}.controller-hotspots{position:absolute;inset:0}.controller-hotspots button{border-style:var(--tw-border-style);border-width:2px;border-color:var(--color-blue-500);background-color:#0b1019f2;border-radius:3.40282e38px;place-items:center;width:30px;height:30px;display:grid;position:absolute}@supports (color:color-mix(in lab, red, red)){.controller-hotspots button{background-color:color-mix(in oklab, var(--color-slate-950) 95%, transparent)}}.controller-hotspots button{--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:10px;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-slate-100);left:var(--x,50%);top:var(--y,50%);transform:translate(-50%, -50%) rotate(var(--label-angle,0deg));padding:0}.controller-hotspots button:hover{z-index:10;background-color:var(--color-slate-800)}.controller-hotspots button.selected{z-index:10;border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-slate-950);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#80afff33}@supports (color:color-mix(in lab, red, red)){.controller-hotspots button.selected{--tw-ring-color:color-mix(in oklab, var(--color-blue-500) 20%, transparent)}}.controller-hotspots button.disabled-map:not(.selected):not(.pressed){border-color:var(--color-slate-500);color:var(--color-slate-400)}.controller-hotspots button.pressed{z-index:20;border-color:var(--color-emerald-400);background-color:var(--color-emerald-400);color:var(--color-slate-950);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#6ed4b133}@supports (color:color-mix(in lab, red, red)){.controller-hotspots button.pressed{--tw-ring-color:color-mix(in oklab, var(--color-emerald-400) 20%, transparent)}}.controller-hotspots button[data-controller-button^=dpad_]{width:26px;height:26px}.controller-annotation{max-width:calc(var(--spacing) * 40);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);background-color:var(--color-slate-800);padding:calc(var(--spacing) * 1.5);text-align:center;left:var(--x);top:var(--y);transform:translate(-50%, -50%) rotate(var(--label-angle,0deg));border-radius:.25rem;font-size:11px;position:absolute}.controller-hint{margin-top:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400)}.extra-controls{margin-top:calc(var(--spacing) * 2);gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.extra-controls button{min-height:calc(var(--spacing) * 11);min-width:calc(var(--spacing) * 11);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:#80afff80}@supports (color:color-mix(in lab, red, red)){.extra-controls button{border-color:color-mix(in oklab, var(--color-blue-500) 50%, transparent)}}.extra-controls button{background-color:var(--color-slate-800);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.extra-controls button.disabled-map{border-color:var(--color-slate-700);color:var(--color-slate-400)}.extra-controls button.selected{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-blue-500)}.extra-controls button.pressed{border-color:var(--color-emerald-400);background-color:var(--color-emerald-400);color:var(--color-slate-950)}.source-unavailable{color:var(--color-amber-400)}.mapping-inspector{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:#80afff40;min-width:0}@supports (color:color-mix(in lab, red, red)){.mapping-inspector{border-color:color-mix(in oklab, var(--color-blue-500) 25%, transparent)}}.mapping-inspector{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 5)}.selected-control-mark{margin-block:calc(var(--spacing) * 4);width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12);border-radius:var(--radius-xl);background-color:#80afff26;place-items:center;display:grid}@supports (color:color-mix(in lab, red, red)){.selected-control-mark{background-color:color-mix(in oklab, var(--color-blue-500) 15%, transparent)}}.selected-control-mark{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-blue-500)}.mapping-inspector>p:not(.eyebrow){margin-top:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.mapping-key{margin-top:calc(var(--spacing) * 6);gap:calc(var(--spacing) * 4);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);flex-wrap:wrap;display:flex}.mapping-key span{align-items:center;gap:calc(var(--spacing) * 2);display:inline-flex}.key-dot{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:2px;border-color:var(--color-blue-500);border-radius:3.40282e38px}.key-dot.disabled{border-color:var(--color-slate-500)}.native-joycon-control,.wii-orientation-control{margin-top:calc(var(--spacing) * 5);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:#17202e80}@supports (color:color-mix(in lab, red, red)){.native-joycon-control,.wii-orientation-control{background-color:color-mix(in oklab, var(--color-slate-850) 50%, transparent)}}.native-joycon-control,.wii-orientation-control{padding:calc(var(--spacing) * 4)}.native-joycon-control>.field-label,.wii-orientation-control>.field-label{margin-top:0}.native-joycon-actions,.wii-orientation-actions{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.native-joycon-actions .select,.wii-orientation-actions .select{flex:240px}.joycon-mode-status,.wii-orientation-status{margin-top:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-emerald-400);overflow-wrap:anywhere}.wii-orientation-status[data-state=idle]{display:none}:is(.joycon-mode-status,.wii-orientation-status):is([data-state=loading],[data-state=locked]){color:var(--color-slate-400)}:is(.joycon-mode-status,.wii-orientation-status):is([data-state=pending],[data-state=unsupported]){color:var(--color-amber-400)}:is(.joycon-mode-status,.wii-orientation-status)[data-state=error]{color:var(--color-rose-400)}.mapping-grid{gap:calc(var(--spacing) * 3);grid-template-columns:repeat(4,minmax(0,1fr));display:grid}.modifier-controls{margin-bottom:calc(var(--spacing) * 5);gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.control-card,.action-card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6399;min-width:0}@supports (color:color-mix(in lab, red, red)){.control-card,.action-card{border-color:color-mix(in oklab, var(--color-slate-700) 60%, transparent)}}.control-card,.action-card{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 4)}.control-card label,.number-field label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-300);display:block}.analog-grid,.feedback-grid,.builtin-actions{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.subpanel{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6399;min-width:0}@supports (color:color-mix(in lab, red, red)){.subpanel{border-color:color-mix(in oklab, var(--color-slate-700) 60%, transparent)}}.subpanel{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 4)}.subpanel-heading{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);flex-wrap:wrap;display:flex}.subpanel-heading>span{max-width:calc(var(--spacing) * 60);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.number-grid{gap:calc(var(--spacing) * 3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.number-field.wide,.toggle-row{grid-column:1/-1}.toggle-row,.check-grid{gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.checkbox-pill{display:inline-block;position:relative}.checkbox-pill input{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.checkbox-pill span{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-900);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-300);display:block}.checkbox-pill input:checked+span{border-color:#80afffb3}@supports (color:color-mix(in lab, red, red)){.checkbox-pill input:checked+span{border-color:color-mix(in oklab, var(--color-blue-500) 70%, transparent)}}.checkbox-pill input:checked+span{background-color:#80afff33}@supports (color:color-mix(in lab, red, red)){.checkbox-pill input:checked+span{background-color:color-mix(in oklab, var(--color-blue-500) 20%, transparent)}}.checkbox-pill input:checked+span{color:var(--color-slate-100)}.checkbox-pill input:focus-visible+span{outline:2px solid var(--color-blue-500);outline-offset:3px}.checkbox-pill input:disabled+span{cursor:not-allowed;opacity:.4}.controller-choice span{min-height:calc(var(--spacing) * 14);min-width:calc(var(--spacing) * 14);justify-content:center;align-items:center;gap:var(--spacing);text-align:center;flex-direction:column;display:flex}.controller-choice b{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.controller-choice small{max-width:calc(var(--spacing) * 20);font-size:10px}.range-row{margin-bottom:calc(var(--spacing) * 4)}.range-meta{margin-bottom:calc(var(--spacing) * 2);justify-content:space-between;gap:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300);display:flex}.range-meta output{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.curve-editor{margin-bottom:calc(var(--spacing) * 4);gap:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6380;grid-template-columns:minmax(0,1fr) minmax(0,1fr);display:grid}@supports (color:color-mix(in lab, red, red)){.curve-editor{border-color:color-mix(in oklab, var(--color-slate-700) 50%, transparent)}}.curve-editor{background-color:var(--color-slate-950);padding:calc(var(--spacing) * 3)}.curve-editor svg{min-height:calc(var(--spacing) * 28);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-slate-700);width:100%;overflow:visible}.curve-editor path{fill:none;vector-effect:non-scaling-stroke}.curve-line{stroke:var(--color-blue-500);stroke-width:2.5px}.curve-guide{stroke:var(--color-slate-500);stroke-dasharray:3 3;opacity:.5}.curve-marker{fill:var(--color-amber-400);stroke:var(--color-slate-950);stroke-width:2px;opacity:0}.curve-marker.visible{opacity:1}.curve-controls{gap:calc(var(--spacing) * 3);flex-direction:column;min-width:0;display:flex}.curve-controls label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300)}.curve-controls output{float:right;color:var(--color-slate-100)}.copy-side{margin-top:auto}.stick-swap-panel{margin-top:calc(var(--spacing) * 4)}.playtest-panel{margin-bottom:calc(var(--spacing) * 5);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:#80afff40}@supports (color:color-mix(in lab, red, red)){.playtest-panel{border-color:color-mix(in oklab, var(--color-blue-500) 25%, transparent)}}.playtest-panel{background-color:#0b101966}@supports (color:color-mix(in lab, red, red)){.playtest-panel{background-color:color-mix(in oklab, var(--color-slate-950) 40%, transparent)}}.playtest-panel{padding:calc(var(--spacing) * 4)}.playtest-heading,.playtest-card-heading{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.playtest-help{margin-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.playtest-status{border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);color:var(--color-amber-400);border-width:1px;border-color:currentColor;font-size:11px}.playtest-panel[data-state=live] .playtest-status{color:var(--color-emerald-400)}.playtest-panel[data-state=error] .playtest-status{color:var(--color-rose-400)}.playtest-panel[data-state=waiting] .playtest-grid{opacity:.5}.playtest-grid{gap:calc(var(--spacing) * 3);grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1.4fr);display:grid}.playtest-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6380;min-width:0}@supports (color:color-mix(in lab, red, red)){.playtest-card{border-color:color-mix(in oklab, var(--color-slate-700) 50%, transparent)}}.playtest-card{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 3)}.playtest-card-heading{margin-bottom:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.playtest-card-heading span{color:var(--color-slate-400);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);font-size:11px}.stick-scope{--raw-x:50%;--raw-y:50%;--output-x:50%;--output-y:50%;--inner-size:0%;--outer-size:100%;aspect-ratio:1;width:100%;max-width:calc(var(--spacing) * 40);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);border-radius:3.40282e38px;margin-inline:auto;position:relative;overflow:hidden}.stick-axis{background-color:#3c4c63b3;position:absolute}@supports (color:color-mix(in lab, red, red)){.stick-axis{background-color:color-mix(in oklab, var(--color-slate-700) 70%, transparent)}}.stick-axis-x{inset-inline:10%;height:1px;top:50%}.stick-axis-y{inset-block:10%;width:1px;left:50%}.stick-ring{pointer-events:none;border-style:var(--tw-border-style);border-width:1px;border-radius:3.40282e38px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.stick-ring-inner{width:var(--inner-size);height:var(--inner-size);border-color:#f19aaacc}@supports (color:color-mix(in lab, red, red)){.stick-ring-inner{border-color:color-mix(in oklab, var(--color-rose-400) 80%, transparent)}}.stick-ring-inner{background-color:#f19aaa0d}@supports (color:color-mix(in lab, red, red)){.stick-ring-inner{background-color:color-mix(in oklab, var(--color-rose-400) 5%, transparent)}}.stick-ring-outer{width:var(--outer-size);height:var(--outer-size);border-color:#80afff99}@supports (color:color-mix(in lab, red, red)){.stick-ring-outer{border-color:color-mix(in oklab, var(--color-blue-500) 60%, transparent)}}.stick-dot{border-radius:3.40282e38px;position:absolute;transform:translate(-50%,-50%)}.stick-dot-raw{left:var(--raw-x);top:var(--raw-y);z-index:10;width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5);border-style:var(--tw-border-style);border-width:2px;border-color:var(--color-amber-400)}.stick-dot-output{left:var(--output-x);top:var(--output-y);z-index:20;width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-950);background-color:var(--color-blue-500)}.trigger-meter{margin-top:calc(var(--spacing) * 3);align-items:center;gap:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));grid-template-columns:24px minmax(0,1fr);display:grid}.trigger-meter output{color:var(--color-slate-400);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);grid-column-start:2;font-size:11px}.trigger-track{--raw:0%;--output:0%;--threshold:35%;height:calc(var(--spacing) * 7);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);position:relative;overflow:hidden}.trigger-bar{height:calc(var(--spacing) * 1.5);border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;position:absolute;left:0}.trigger-bar-raw{width:var(--raw);top:var(--spacing);background-color:var(--color-amber-400)}.trigger-bar-output{width:var(--output);bottom:var(--spacing);background-color:var(--color-blue-500)}.trigger-threshold{left:var(--threshold);width:calc(var(--spacing) * .5);background-color:var(--color-rose-400);position:absolute;inset-block:0}.playtest-legend{margin-top:calc(var(--spacing) * 4);gap:calc(var(--spacing) * 3);color:var(--color-slate-400);flex-wrap:wrap;font-size:11px;display:flex}.playtest-legend span{align-items:center;gap:calc(var(--spacing) * 1.5);display:inline-flex}.playtest-legend i{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);border-radius:3.40282e38px}.legend-raw{background-color:var(--color-amber-400)}.legend-output{background-color:var(--color-blue-500)}.legend-threshold{background-color:var(--color-rose-400)}.mapping-playtest{margin-top:calc(var(--spacing) * 4)}:where(.mapping-playtest>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.mapping-playtest{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300)}.turbo-grid{gap:calc(var(--spacing) * 3);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.turbo-setting-grid{margin-top:calc(var(--spacing) * 3);gap:calc(var(--spacing) * 2);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.turbo-setting-grid .number-input{padding-inline:calc(var(--spacing) * 2)}.turbo-setting-grid label{min-height:calc(var(--spacing) * 8)}.turbo-override{margin-block:calc(var(--spacing) * 3);display:block}.turbo-defaults{margin-bottom:calc(var(--spacing) * 4)}.timing-notice{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-850);padding:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400)}.timing-notice.warning,.turbo-card.narrow-pulse{border-color:#ebc17480}@supports (color:color-mix(in lab, red, red)){.timing-notice.warning,.turbo-card.narrow-pulse{border-color:color-mix(in oklab, var(--color-amber-400) 50%, transparent)}}.timing-notice.warning{color:var(--color-amber-400)}.builtin-actions,.action-card-heading{margin-bottom:calc(var(--spacing) * 4)}.action-card-heading>span{margin-top:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-400);display:block}.action-kind{--tw-font-weight:var(--font-weight-medium);font-size:11px;font-weight:var(--font-weight-medium);color:var(--color-blue-500)}.swing-controls{margin-bottom:calc(var(--spacing) * 5)}.swing-cards{margin-bottom:calc(var(--spacing) * 4);gap:calc(var(--spacing) * 3);flex-wrap:wrap;display:flex}.swing-card{flex:280px;min-width:0}.swing-card:last-child{flex-basis:100%}.swing-card-grid{gap:calc(var(--spacing) * 3);display:grid}.swing-card:last-child .swing-card-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.macro-controls{margin-bottom:calc(var(--spacing) * 5);gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.macro-picker{align-items:stretch;gap:calc(var(--spacing) * 4);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding:calc(var(--spacing) * 3);flex-wrap:wrap;grid-column:1/-1;display:flex}.macro-tabs{gap:calc(var(--spacing) * 2);flex:320px;grid-template-columns:repeat(4,minmax(0,1fr));display:grid}.macro-tab{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-850);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-300)}.macro-tab small{margin-top:var(--spacing);font-size:10px;display:block}.macro-tab.selected{border-color:#80afff99}@supports (color:color-mix(in lab, red, red)){.macro-tab.selected{border-color:color-mix(in oklab, var(--color-blue-500) 60%, transparent)}}.macro-tab.selected{background-color:#80afff33}@supports (color:color-mix(in lab, red, red)){.macro-tab.selected{background-color:color-mix(in oklab, var(--color-blue-500) 20%, transparent)}}.macro-tab.selected{color:var(--color-slate-100)}.macro-budget{justify-content:center;gap:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);flex-direction:column;flex:0 180px;display:flex}.macro-budget strong{color:var(--color-slate-100)}.macro-playback-fields{margin-top:calc(var(--spacing) * 4);gap:calc(var(--spacing) * 3);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.macro-steps-heading,.macro-preview-heading{margin-bottom:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);flex-wrap:wrap;display:flex}.macro-step-action,.macro-preview-actions{gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.macro-steps{gap:calc(var(--spacing) * 4);display:grid}.macro-step{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-850);min-width:0;padding:calc(var(--spacing) * 4)}.macro-step.end{--tw-border-style:dashed;padding:calc(var(--spacing) * 5);border-style:dashed}.step-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);flex-wrap:wrap;display:flex}.step-identity,.step-tools{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.step-number{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-100)}.step-timing{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.step-tool,.remove-step{min-height:calc(var(--spacing) * 9);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300)}.remove-step{border-color:#f19aaa4d}@supports (color:color-mix(in lab, red, red)){.remove-step{border-color:color-mix(in oklab, var(--color-rose-400) 30%, transparent)}}.remove-step{color:var(--color-rose-400)}.step-handle{cursor:grab}.step-handle:active{cursor:grabbing}.step-handle{touch-action:pan-y}.step-grid{gap:calc(var(--spacing) * 3);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.step-group{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:calc(var(--spacing) * 4)}.step-group-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-300);display:block}.macro-step.dragging{opacity:.4}.macro-step.drop-before{box-shadow:0 -4px 0 var(--color-blue-500)}.macro-step.drop-after{box-shadow:0 4px 0 var(--color-blue-500)}.macro-step.preview-active{border-color:var(--color-emerald-400)}.macro-notice,.capture-notice{margin-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);color:var(--color-slate-300)}.macro-notice:empty,.capture-notice:empty,.capture-events:empty{display:none}.macro-notice.error{color:var(--color-rose-400)}.macro-preview,.macro-capture{margin-bottom:calc(var(--spacing) * 5);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:#0b101966}@supports (color:color-mix(in lab, red, red)){.macro-preview,.macro-capture{background-color:color-mix(in oklab, var(--color-slate-950) 40%, transparent)}}.macro-preview,.macro-capture{padding:calc(var(--spacing) * 4)}.macro-preview[data-state=playing]{border-color:var(--color-emerald-400)}.macro-capture[data-state=recording]{border-color:var(--color-rose-400)}.macro-preview>.field-help,.macro-capture>.field-help{margin-block:calc(var(--spacing) * 3)}.macro-preview-timing{margin-block:calc(var(--spacing) * 3);justify-content:space-between;gap:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);flex-wrap:wrap;display:flex}.macro-preview-buttons{margin-block:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6380}@supports (color:color-mix(in lab, red, red)){.macro-preview-buttons{border-color:color-mix(in oklab, var(--color-slate-700) 50%, transparent)}}.macro-preview-buttons{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 3)}.preview-button{min-width:calc(var(--spacing) * 14);gap:var(--spacing);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);flex-direction:column;display:inline-flex}.preview-button small{font-size:10px}.preview-button.pressed{border-color:var(--color-emerald-400);background-color:var(--color-emerald-400);color:var(--color-slate-950)}.macro-preview [data-mode=override]{border-color:#6ed4b1b3}@supports (color:color-mix(in lab, red, red)){.macro-preview [data-mode=override]{border-color:color-mix(in oklab, var(--color-emerald-400) 70%, transparent)}}.macro-preview [data-mode=override] [data-preview-mode]{color:var(--color-emerald-400)}.macro-preview [data-mode=passthrough] .stick-dot-output{visibility:hidden}.macro-preview [data-mode=passthrough] progress{opacity:.35}.macro-preview [data-preview-value]{margin-top:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);display:block}.macro-preview-triggers{justify-content:center;gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.macro-preview-triggers>div{border-radius:var(--radius-lg);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--color-slate-700);padding:calc(var(--spacing) * 3)}.capture-options{margin-block:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);min-width:0;padding:calc(var(--spacing) * 3)}.capture-options legend{padding-inline:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-300)}.capture-options:disabled{opacity:.6}.capture-number-fields{margin-top:calc(var(--spacing) * 4);gap:calc(var(--spacing) * 3);grid-template-columns:repeat(3,minmax(0,1fr));display:grid}.capture-events{margin-block:calc(var(--spacing) * 3);max-height:calc(var(--spacing) * 64);list-style-type:decimal}:where(.capture-events>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.capture-events{padding-left:calc(var(--spacing) * 6);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-slate-400);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);overflow:auto}.capture-events li{border-style:var(--tw-border-style);border-width:1px;border-color:#3c4c6380;border-radius:.25rem}@supports (color:color-mix(in lab, red, red)){.capture-events li{border-color:color-mix(in oklab, var(--color-slate-700) 50%, transparent)}}.capture-events li{background-color:var(--color-slate-850);padding:calc(var(--spacing) * 2);overflow-wrap:anywhere}.macro-capture:is([data-state=full],[data-state=disconnected],[data-state=error]) .capture-notice{color:var(--color-amber-400)}.loading-card p{max-width:var(--container-md);padding-inline:calc(var(--spacing) * 4);text-align:center;--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);overflow-wrap:anywhere}body:has(.connection[data-state=error]) .loading-card .spinner{display:none}#profileForm [inert]{opacity:.5}.loading-card{min-height:calc(var(--spacing) * 72);place-content:center;justify-items:center;gap:calc(var(--spacing) * 4);border-radius:var(--radius-xl);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--color-slate-700);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-slate-400);display:grid}.spinner{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7);animation:var(--animate-spin);border-style:var(--tw-border-style);border-width:2px;border-color:var(--color-slate-700);border-top-color:var(--color-blue-500);border-radius:3.40282e38px}.toast{pointer-events:none;right:calc(var(--spacing) * 5);bottom:calc(var(--spacing) * 5);z-index:50;--tw-translate-y:calc(var(--spacing) * 3);max-width:min(440px,100vw - 40px);translate:var(--tw-translate-x) var(--tw-translate-y);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-850);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-slate-100);opacity:0;--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));position:fixed}.toast.show{--tw-translate-y:0;translate:var(--tw-translate-x) var(--tw-translate-y);opacity:1}.toast.error{border-color:var(--color-rose-400);color:var(--color-rose-400)}dialog{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-900);width:min(480px,100% - 32px);max-height:calc(100dvh - 32px);padding:calc(var(--spacing) * 6);color:var(--color-slate-100);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);margin:auto;position:fixed;inset:0;overflow-y:auto}.adapter-settings-dialog{width:min(600px,100% - 32px)}dialog::backdrop{background-color:#0b1019cc}@supports (color:color-mix(in lab, red, red)){dialog::backdrop{background-color:color-mix(in oklab, var(--color-slate-950) 80%, transparent)}}.dialog-actions{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 2);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:calc(var(--spacing) * 4);flex-wrap:wrap;display:flex}.joycon-mode{margin-top:calc(var(--spacing) * 5);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:calc(var(--spacing) * 5)}}@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.-mt-0\.5{margin-top:calc(var(--spacing) * -.5)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.flex{display:flex}.hidden{display:none}.inline-block{display:inline-block}.h-4{height:calc(var(--spacing) * 4)}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing) * 4)}.w-full{width:100%}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rounded{border-radius:.25rem}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-slate-800{border-color:var(--color-slate-800)}.bg-slate-950{background-color:var(--color-slate-950)}.pt-2{padding-top:calc(var(--spacing) * 2)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.text-slate-100{color:var(--color-slate-100)}.text-slate-400{color:var(--color-slate-400)}.text-white{color:var(--color-white)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.selection\:bg-sky-500\/30 ::selection{background-color:#80afff4d}@supports (color:color-mix(in lab, red, red)){.selection\:bg-sky-500\/30 ::selection{background-color:color-mix(in oklab, var(--color-sky-500) 30%, transparent)}}.selection\:bg-sky-500\/30::selection{background-color:#80afff4d}@supports (color:color-mix(in lab, red, red)){.selection\:bg-sky-500\/30::selection{background-color:color-mix(in oklab, var(--color-sky-500) 30%, transparent)}}.selection\:text-sky-200 ::selection{color:var(--color-sky-200)}.selection\:text-sky-200::selection{color:var(--color-sky-200)}.focus\:not-sr-only:focus{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.focus\:fixed:focus{position:fixed}.focus\:top-3:focus{top:calc(var(--spacing) * 3)}.focus\:left-3:focus{left:calc(var(--spacing) * 3)}.focus\:z-50:focus{z-index:50}.focus\:rounded-lg:focus{border-radius:var(--radius-lg)}.focus\:bg-sky-500:focus{background-color:var(--color-sky-500)}.focus\:px-4:focus{padding-inline:calc(var(--spacing) * 4)}.focus\:py-2:focus{padding-block:calc(var(--spacing) * 2)}.focus\:font-bold:focus{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.focus\:text-slate-950:focus{color:var(--color-slate-950)}.focus\:shadow-lg:focus{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-white:focus{--tw-ring-color:var(--color-white)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:40rem){.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}}@media (max-width:1279px){.workspace{grid-template-columns:220px minmax(0,1fr);gap:16px;padding-inline:16px}.controller-mapper{grid-template-columns:minmax(0,1fr)}.mapping-inspector{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:8px 16px;display:grid}.mapping-inspector .eyebrow,.mapping-inspector h4,.mapping-inspector>p,.mapping-key{grid-column:1/-1}.mapping-inspector .field-label{margin-top:8px}.selected-control-mark{display:none}.mapping-grid,.turbo-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.curve-editor{grid-template-columns:1fr}.playtest-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.trigger-playtest,.macro-preview-triggers{grid-column:1/-1}}@media (max-width:899px){.topbar{flex-wrap:wrap;gap:16px;padding-block:16px;position:relative}.topbar-meta{flex-wrap:wrap}.workspace{grid-template-columns:minmax(0,1fr)}.sidebar{max-height:none;position:static}.profile-list{grid-template-columns:repeat(4,minmax(0,1fr))}.profile-button{flex-wrap:wrap}.identity-tools{margin-top:16px}.section-nav{top:0}.panel{scroll-margin-top:72px}.header-actions{align-items:flex-start}}@media (max-width:599px){.topbar{padding-inline:16px}.topbar-meta{justify-content:space-between;gap:8px;width:100%}.connection{max-width:48%}.workspace{margin-block:16px;padding-inline:12px}.sidebar,.panel,.editor-header{padding:16px}.profile-list{grid-template-columns:repeat(2,minmax(0,1fr))}.panel-heading{flex-direction:column;gap:12px}.panel-heading-actions{text-align:left;align-items:flex-start}.controller-canvas{padding:12px}.mapping-inspector{display:block}.selected-control-mark{display:grid}.mapping-grid,.modifier-controls,.analog-grid,.feedback-grid,.builtin-actions,.macro-controls,.turbo-grid,.playtest-grid,.swing-card:last-child .swing-card-grid,.capture-number-fields,.number-grid,.step-grid{grid-template-columns:minmax(0,1fr)}.macro-tabs{grid-template-columns:repeat(2,minmax(0,1fr))}.button,.step-tool,.remove-step,.checkbox-pill span{min-height:44px}.subpanel-heading{flex-direction:column}.native-joycon-actions .button{flex:auto}.header-actions,.action-group-primary{width:100%}.action-group-primary .button{flex:1}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/src/switch_pico_bridge/web/profile_editor.html b/src/switch_pico_bridge/web/profile_editor.html index 45dac53..04b012b 100644 --- a/src/switch_pico_bridge/web/profile_editor.html +++ b/src/switch_pico_bridge/web/profile_editor.html @@ -3,160 +3,161 @@ - Switch Pico Profile Studio + Switch Pico Controller Studio - -
-
+ + + + Skip to main content + + +
- +

Switch Pico

-

Controller Studio

+

Controller Studio

+
-

Map · tune · automate

- - Connecting to adapter… + + Connecting to adapter…
- + +
-
+ +
+ +
+
-
-

Global fallback

-
-

Profile 1

+
+

Default profile

+
+

Profile 1

- +
- - - + + +
+
- - - - - - - +
+ + + + + +
+
+ + +
+ +
-

Reading profile from the adapter…

+

Reading profile from the adapter…

+
+ +
-

Adapter settings

+
+

Hardware configuration

+

Adapter settings

+
-

These settings apply to the whole adapter, not the selected profile. Use Apply to save changes; closing keeps unapplied selections.

-
-

Adapter-wide setting

-

Joy-Con 2 default mode

- - -

Saved on the adapter; changing it applies live and clears temporary joins/splits. Not an artwork preview.

-
- Controller shortcut & profile banks -
-

Paired: L + R share one player and a separate L+R profile bank. Unmatched halves stay separate.

-

Individual: each half is a sideways player using its own solo bank. Choose the L or R profile owner to edit it.

-

Join / split this pair: hold ZL + Minus on the left and ZR + Plus on the right for 2 seconds. A short pulse confirms success. Release all four buttons before using it again.

-

The shortcut affects only those halves and overrides this default until either disconnects. Other Joy-Cons are unchanged. Each half's complete two-button chord is reserved until both buttons release, even while waiting for the other half. Ambiguous solo selections do not join.

-

All saved banks and unsaved profile drafts are kept. Your chosen owner does not change automatically.

-

Saved preference is not a live pairing report. A pair waits for its L+R bank to be ready; the diagram and live playtest report the actual arrangement.

-
-
- -

Reading saved preference…

-
+

These settings apply to the whole adapter. Apply saves them immediately; closing keeps your unapplied selection without changing the adapter.

+ +
+

Adapter default

+

Joy-Con 2 default mode

+ + +

Stored in Pico flash; applying changes resets temporary Joy-Con splits or joins.

+ +
+ Join/split shortcut & profile behavior +
+

Paired: Left and Right Joy-Cons share one player identity and a combined L+R profile bank.

+

Individual: Each half functions as an independent sideways controller using its own solo profile bank.

+

Quick join / split: Hold ZL + Minus on (L) and ZR + Plus on (R) for 2 seconds. A short rumble pulse confirms the change.

+

Release all four buttons before using the shortcut again. It affects only these halves and overrides the default until either disconnects; other Joy-Cons are unchanged. Unmatched halves stay separate.

+

Paired and solo profiles are independent. All saved profiles and unsaved drafts are kept. The saved default is not a live pairing report; the diagram shows the current arrangement.

+
+
+
+ +
+

Reading saved preference…

+
- -
-

Duplicate profile

-

Choose a destination

-

Copy this current draft, including unsaved settings, to the destination. This replaces its stored profile and name; the source draft is unchanged.

- - - - -
+ + + +
+

Duplicate profile

+

Choose destination

+
+

Copy this draft, including unsaved changes, to another profile. This immediately replaces the destination’s saved settings and name. The source draft is unchanged.

+ +
+ + +
+
+ + +
+ +
+ +
+ + diff --git a/src/switch_pico_bridge/web/profile_editor.js b/src/switch_pico_bridge/web/profile_editor.js index ccaa737..292ca1d 100644 --- a/src/switch_pico_bridge/web/profile_editor.js +++ b/src/switch_pico_bridge/web/profile_editor.js @@ -202,6 +202,64 @@ const elements = { toast: document.querySelector("#toast"), }; +const sectionLinks = document.querySelectorAll(".section-nav a"); +const sectionPanels = Array.from(sectionLinks, (link) => + document.getElementById(link.hash.slice(1)) +); +let selectedSection = null; + +function showSection(id) { + const panel = sectionPanels.find((section) => section.id === id) || sectionPanels[0]; + if (selectedSection === panel) return; + if (selectedSection) { + stopMacroPreview("Preview stopped: section changed."); + clearMacroDrag(); + stopCapture("Recording stopped because you navigated to another section."); + } + selectedSection = panel; + sectionPanels.forEach((section) => { + section.hidden = section !== panel; + }); + sectionLinks.forEach((link) => { + if (link.hash === `#${panel.id}`) { + link.setAttribute("aria-current", "page"); + // Keep the current section visible in the horizontally scrolling mobile nav. + link.parentElement.scrollLeft = link.offsetLeft - (link.parentElement.clientWidth - link.offsetWidth) / 2; + } else link.removeAttribute("aria-current"); + }); +} + +function showSectionFromHash() { + showSection(window.location.hash.slice(1)); +} + +function revealInvalidControl(control) { + // A modal copy dialog would otherwise keep the invalid draft field inert. + if (elements.copyDialog.open) elements.copyDialog.close(); + const panel = sectionPanels.find((section) => section.contains(control)); + if (panel) { + showSection(panel.id); + if (window.location.hash !== `#${panel.id}`) window.location.hash = panel.id; + } + for (let ancestor = control.parentElement; ancestor && ancestor !== elements.form; ancestor = ancestor.parentElement) { + if (ancestor.tagName === "DETAILS") ancestor.open = true; + if (ancestor.hidden) ancestor.hidden = false; + } +} + +function reportProfileValidity() { + // Validate only this form's controls, not the separate recording settings. + // Report one field at a time so other hidden panels never receive focus. + for (const control of elements.form.elements) { + if (control.willValidate && !control.validity.valid) { + revealInvalidControl(control); + control.reportValidity(); + return false; + } + } + return true; +} + function escapeHtml(value) { return String(value) .replaceAll("&", "&") @@ -282,8 +340,8 @@ function syncControllerPresentation() { const layout = ControllerLayouts[layoutId] || ControllerLayouts.generic; const reported = live?.source_controls || owner?.source_controls || state.schema.controls; const sources = preview ? Object.keys(layout.controls) : reported.filter(control => - layout.generic || Object.hasOwn(layout.controls, control)); - const topologyKnown = layoutId !== "wii-remote" && + layout.generic || layout.referenceOnly || Object.hasOwn(layout.controls, control)); + const topologyKnown = !layout.referenceOnly && layoutId !== "wii-remote" && (!(layoutId.startsWith("joycon2-") || layoutId.startsWith("wii-")) || live?.layout === layoutId); const liveDiagram = Boolean(live && !preview && topologyKnown && !layout.generic); const style = preview ? layout.style : controller.style || layout.style; @@ -459,8 +517,8 @@ function renderPlaytest(sample) { clearPlaytest( sample.connected ? sample.identity?.is_joycon_pair - ? "Paired input belongs to the L+R profile owner. Select that owner for paired settings; this draft and its layout are unchanged." - : "Live input belongs to another profile owner; this draft and its layout are unchanged." + ? "Paired input uses the L+R controller profiles. Select L+R for paired settings; this draft and its layout are unchanged." + : "Live input belongs to another controller; this draft and its layout are unchanged." : "Connect or move the selected controller to compare its raw input with this draft." ); return; @@ -626,7 +684,7 @@ async function pollPlaytest() { profileIndex === state.profileIndex ) { clearPlaytest( - `${error.message}. Flash current firmware to enable live playtest.`, + `Live input unavailable: ${error.message}`, "error" ); } @@ -643,24 +701,38 @@ function isDirty() { } function setConnection(mode, text) { + if (mode === "ready" && document.hidden) { + mode = "loading"; + text = "Connection check paused"; + } const wasConnected = state.adapterConnected; state.adapterConnected = mode === "ready"; - if (wasConnected && !state.adapterConnected) stopMacroPreview("Preview stopped: adapter disconnected."); - if (wasConnected && !state.adapterConnected && captureBlocking()) { - stopCapture("Adapter disconnected; retaining the last received recording."); + if (wasConnected && !state.adapterConnected) { + stopMacroPreview("Preview stopped: adapter disconnected."); + if (captureBlocking()) { + stopCapture("Adapter disconnected; retaining the last received recording."); + } + clearPlaytest("Adapter connection unavailable. Your draft is retained.", "error"); } elements.connection.dataset.state = mode; elements.connectionText.textContent = text; - elements.copyProfile.disabled = state.busy || !state.adapterConnected; - if (state.profile) { - elements.save.disabled = - !state.adapterConnected || state.busy || !isDirty(); - elements.activate.disabled = - !state.adapterConnected || state.busy || state.active; - } + updateDeviceActions(); renderCapture(); } +function updateDeviceActions() { + const unavailable = state.busy || captureBlocking() || !state.adapterConnected; + elements.save.disabled = unavailable || !isDirty(); + elements.activate.disabled = unavailable || !state.profile || state.active; + elements.copyProfile.disabled = unavailable || !state.profile; + elements.confirmCopy.disabled = unavailable || !state.profile; + elements.saveProfileName.disabled = unavailable || !state.profile; + elements.saveAlias.disabled = unavailable || currentOwner()?.index === 0; + elements.identify.disabled = unavailable || !state.identifyAvailable; + renderJoyconMode(); + renderWiiOrientation(); +} + let toastTimer = 0; function toast(message, error = false) { window.clearTimeout(toastTimer); @@ -675,7 +747,16 @@ async function api(path, options = {}) { if (options.method && options.method !== "GET") { headers["X-Switch-Pico-Token"] = state.token; } - const response = await fetch(path, { ...options, headers }); + let response; + try { + response = await fetch(path, { ...options, headers }); + } catch (error) { + setConnection("error", "Editor server unavailable"); + throw error; + } + // USB failures are connection failures, not unsupported playtest features. + // Ordinary 400 validation/feature errors must not disconnect a healthy Pico. + if (response.status === 503) setConnection("error", "Adapter unavailable"); let payload; try { payload = await response.json(); @@ -703,7 +784,7 @@ function renderJoyconMode() { let status; let mode; const saved = joyconMode.saved === null ? "" : - `Saved default: ${label(joyconMode.saved)} · generation ${joyconMode.generation}.`; + `Saved default: ${label(joyconMode.saved)}.`; const selection = joyconModeChanged() ? ` Selection: ${label(joyconMode.selected)} — not applied.` : ""; if (joyconMode.applying) { @@ -818,7 +899,7 @@ function renderWiiOrientation() { elements.wiiOrientation.disabled = true; elements.applyWiiOrientation.disabled = true; elements.wiiOrientationStatus.dataset.state = "unsupported"; - elements.wiiOrientationStatus.textContent = "Default profile owner cannot set orientation. Select the connected Wii Remote."; + elements.wiiOrientationStatus.textContent = "Select the connected Wii Remote instead of the default profiles to set orientation."; return; } @@ -854,29 +935,20 @@ function renderWiiOrientation() { function setBusy(busy) { state.busy = busy; busy ||= captureBlocking(); - elements.save.disabled = - busy || !state.adapterConnected || !isDirty(); - elements.activate.disabled = - busy || !state.adapterConnected || state.active; elements.resetDraft.disabled = busy; elements.refresh.disabled = busy; elements.identity.disabled = busy; - elements.identify.disabled = busy || !state.identifyAvailable; elements.profileName.disabled = busy; - elements.saveProfileName.disabled = busy || !state.adapterConnected; elements.controllerAlias.disabled = busy || currentOwner()?.index === 0; - elements.saveAlias.disabled = - busy || !state.adapterConnected || currentOwner()?.index === 0; - elements.copyProfile.disabled = busy || !state.adapterConnected; elements.importProfile.disabled = busy; elements.exportProfile.disabled = busy; if (state.schema && state.profile) updateMacroBudgets(); document.querySelectorAll(".profile-button").forEach((button) => { button.disabled = busy; }); + updateDeviceActions(); renderCapture(); - renderWiiOrientation(); renderNativeLayout(); } @@ -927,7 +999,7 @@ function syncLibraryMetadata(identities, restoreStoredOwner = false) { restoreStoredOwner ? storedOwnerKey() : oldOwner?.key ) || storedOwnerKey(); if (oldOwner && state.profile && !identities.some((entry) => entry.key === oldOwner.key)) { - throw new Error("The selected profile owner is unavailable. Your draft is retained; reconnect the owner or export it before choosing another."); + throw new Error("The selected controller is unavailable. Your draft is retained; reconnect the controller or export the draft before choosing another."); } state.identities = identities; const nextOwner = ( @@ -1004,8 +1076,8 @@ function renderProfileList() { const selected = index === state.profileIndex; const active = owner && owner.active_profile === index + 1; return ` - `; }).join(""); @@ -1145,7 +1217,7 @@ function renderShortcuts() {
-

None clears every shortcut. Switch 2 extra inputs can act as modifiers. Trigger modifiers are available for known Xbox, PlayStation and Switch 2 owners.

+

None clears every shortcut. Switch 2 extra inputs can act as modifiers. Trigger modifiers are available for recognized Xbox, PlayStation and Switch 2 controllers.

`; elements.shortcuts.innerHTML = shortcuts.profiles.map((selector, index) => `
@@ -1277,21 +1349,23 @@ function renderButtonMap() { elements.controllerCanvas.dataset.layout = layoutId; elements.controllerLayoutStatus.dataset.state = liveDiagram ? "live" : "preview"; elements.controllerLayoutStatus.textContent = preview ? "Manual layout preview" : - liveDiagram ? "Live · matching owner" : live ? "Live input · reference diagram" : "Offline · owner reference"; + liveDiagram ? "Live · selected controller" : live ? "Live input · reference diagram" : "Offline · controller reference"; elements.controllerModel.textContent = layout.name; elements.controllerLayoutHelp.textContent = preview - ? `Manual preview only${live ? `; connected input is ${live.controller.model}` : ""}. No topology or saved mapping is changed. Physical highlighting is off.` + ? `Layout preview only${live ? `; connected input is ${live.controller.model}` : ""}. The controller setup and saved mappings are unchanged. Physical highlighting is off.` + : layout.referenceOnly + ? "Wii-family reference only: orientation and extensions are unknown. Connect the selected controller for its detected layout. No mappings are changed or hidden." : live && !topologyKnown ? layoutId === "wii-remote" ? "Legacy firmware does not report Wii orientation. This reference shows the default horizontal mapping; physical highlighting is off. Choose a preview or update firmware." - : "Legacy firmware does not report Joy-Con pair/solo topology. This is an owner reference, not detected solo mode. Choose a preview or update firmware." - : "Auto uses only the selected owner's metadata. Preview changes this editor's source labels and diagram, never firmware topology or saved mappings."; + : "Legacy firmware does not report paired or solo Joy-Con mode. This diagram is a controller reference, not a detected solo layout. Choose a preview or update firmware." + : "Auto uses the selected controller's details. Preview changes source labels and the diagram only, never the controller setup or saved mappings."; elements.controllerLayoutNote.textContent = !preview && live?.layout === "joycon2-pair" && !live.identity?.is_joycon_pair - ? "This older firmware reports paired input through a physical controller's profile bank. There is no independent L+R bank; edits still affect the selected existing owner. Update firmware for separate pair profiles. The grip is illustrative, not detected." + ? "This older firmware uses an individual controller's profiles for paired input. There are no separate L+R profiles; edits still affect the selected controller. Update firmware for separate pair profiles. The grip is illustrative, not detected." : layout.note || - (layout.generic ? "Generic reference art is not a model identification. Additional reported sources are unlocated, not buttons on this drawing." : - "Rear triggers are listed off-art below the front view. Unavailable stored mappings are retained."); + (layout.generic ? "This is a generic reference, not an identified controller model. Additional reported inputs are listed separately from the diagram." : + "Rear triggers are listed below the front view. Saved mappings for unavailable controls are retained."); const diagramKey = `${layoutId}:${sources.join(",")}`; if (diagramKey !== state.diagramKey) { const focusedSource = elements.controllerCanvas.contains(document.activeElement) @@ -1346,7 +1420,7 @@ function renderButtonMap() { hotspot.classList.toggle("disabled-map", output == null); if (!liveDiagram) hotspot.classList.remove("pressed"); hotspot.setAttribute("aria-pressed", String(button === selected)); - hotspot.title = `${sourceLabel(button)} [${button}] → ${output == null ? "Disabled" : outputLabel(output)}`; + hotspot.title = `${sourceLabel(button)} → ${output == null ? "Disabled" : outputLabel(output)}`; hotspot.setAttribute("aria-label", hotspot.title); }); elements.selectedSource.innerHTML = sourceOptions(selected); @@ -1354,7 +1428,7 @@ function renderButtonMap() { elements.selectedControlGlyph.textContent = sourceGlyph(selected); elements.selectedControlName.textContent = sourceLabel(selected); elements.selectedControlDescription.textContent = - `${sourceAvailable(selected) ? "Source" : "Stored / unavailable source"} ${sourceLabel(selected)} [${selected}] produces ${mappedOutput == null ? "no output" : outputLabel(mappedOutput)}.${sourceAvailable(selected) ? "" : " Its mapping is retained; change the preview to locate it."}`; + `${sourceLabel(selected)} produces ${mappedOutput == null ? "no output" : outputLabel(mappedOutput)}.${sourceAvailable(selected) ? "" : " This control is unavailable in the current layout; its saved mapping is retained."}`; elements.selectedMapping.innerHTML = buttonOptions(mappedOutput, true, state.schema.output_controls, style); renderWiiOrientation(); } @@ -1613,7 +1687,7 @@ function renderTurbo() { const override = settings.overrides[button]; const off = state.profile.turbo[button] === "off"; return `
- + @@ -1745,7 +1819,7 @@ function macroBudgetError(macros) { } const budget = macroBudget(macros); if (budget.steps > MACRO_SHARED_STEP_LIMIT) return "The four macros share a limit of 16 steps."; - if (budget.bytes > MACRO_BYTE_LIMIT) return "The four macros share a limit of 136 sparse bytes."; + if (budget.bytes > MACRO_BYTE_LIMIT) return "The four macros share 136 bytes of storage."; return ""; } @@ -1825,7 +1899,7 @@ function renderCapture() { elements.captureProgress.max = Math.max(1, options.max_events); elements.captureProgress.value = count; elements.captureBudget.textContent = - `${count}/${options.max_events} recorded states · ${count * options.stepBytes}/${options.max_events * options.stepBytes} reserved sparse bytes · ` + + `${count}/${options.max_events} recorded states · ${count * options.stepBytes}/${options.max_events * options.stepBytes} reserved bytes · ` + `other macros: ${options.otherBudget.steps}/16 shared steps, ${options.otherBudget.bytes}/136 bytes. ` + "Initial input counts as a state; held states split at 10 seconds. Capture stops before exceeding capacity."; const terminalNotice = { @@ -1840,7 +1914,7 @@ function renderCapture() { elements.captureNotice.textContent = [ session?.error, terminalNotice, page?.conversion_error, session?.message, originWarning, - session?.used ? "Recorded steps are now in the unsaved draft. Review the visual preview below; Save remains a separate action." : "", + session?.used ? "Recorded steps are in the unsaved draft. Review the preview, then use Save to Pico to store the profile." : "", !session && !sourceReady ? "Connect the selected controller and wait for its live input before recording." : "", !session && !options.channels ? "Select at least one channel." : "", !session && !options.max_events ? "Other macros leave no room for a recorded state. Free shared steps or bytes first." : "", @@ -1917,7 +1991,7 @@ function finishCaptureRequest(session) { async function beginCapture() { if (elements.captureRecord.disabled || state.busy || macroCapture.session || state.playtestRequestActive || state.libraryRequestActive) return; - if (!elements.form.reportValidity() || !macroInputsValid(true)) return; + if (!reportProfileValidity() || !macroInputsValid(true)) return; const options = captureOptions(); if (!options.channels || !options.max_events) return; const owner = currentOwner(); @@ -2013,7 +2087,7 @@ async function stopCapture(message = "") { async function useCapture() { const session = macroCapture.session; if (!session?.terminal || macroCapture.requestActive || !captureOriginMatches(session)) return; - if (!session.page?.steps?.length || !elements.form.reportValidity()) return; + if (!session.page?.steps?.length || !reportProfileValidity()) return; const candidate = clone(state.profile); candidate.macros[session.macroIndex].steps = clone(session.page.steps); const error = macroBudgetError(candidate.macros); @@ -2086,7 +2160,7 @@ function updateMacroBudgets() { `${budget.steps}/${MACRO_SHARED_STEP_LIMIT} shared steps` ); elements.macroControls.querySelector("[data-budget-bytes]")?.replaceChildren( - `${budget.bytes}/${MACRO_BYTE_LIMIT} sparse bytes` + `${budget.bytes}/${MACRO_BYTE_LIMIT} bytes` ); const progress = elements.macroControls.querySelector("[data-budget-progress]"); if (progress) progress.value = budget.bytes; @@ -2100,7 +2174,7 @@ function updateMacroBudgets() { elements.macroDuration.textContent = `${macro.steps.length}/8 steps · ${bytes} bytes · ${duration} ms total · ${budget.duration} ms across all macros`; elements.addMacroStep.disabled = state.busy || !canAddMacroStep(DEFAULT_MACRO_STEP_BYTES, budget); - elements.addMacroStep.title = "Add a 100 ms button step (5 sparse bytes)"; + elements.addMacroStep.title = "Add a 100 ms button step (5 bytes)"; let startsAt = 0; elements.macroSteps.querySelectorAll("[data-step-index]").forEach((card) => { const index = Number(card.dataset.stepIndex); @@ -2132,7 +2206,10 @@ function updateMacroBudgets() { function macroInputsValid(report = false) { const invalid = elements.macroSteps.querySelector("input:invalid") || elements.macroControls.querySelector("input:invalid"); - if (report && invalid) invalid.reportValidity(); + if (report && invalid) { + revealInvalidControl(invalid); + invalid.reportValidity(); + } return !invalid; } @@ -2306,7 +2383,7 @@ function renderMacro() { actionChordCard( "profile_switch", "Cycle active profile", - `Advance through profile slots 1–${state.schema.profile_capacity}.`, + `Cycle through profiles 1–${state.schema.profile_capacity}.`, state.profile.switching_chord, state.schema.default_switching_chord ), @@ -2432,8 +2509,8 @@ function renderMacro() {
${budget.steps}/16 shared steps - ${budget.bytes}/136 sparse bytes - + ${budget.bytes}/136 bytes +
${actionChordCard( @@ -2485,7 +2562,7 @@ function renderMacroSteps() { const controllerStyle = currentControllerStyle(); elements.macroSteps.innerHTML = macro.steps.length === 0 - ? '

No steps yet. Add a state step to build this macro.

' + ? '

No steps yet. Add a step or record controller input to build this macro.

' : macro.steps.map((step, index) => { const overrides = new Set(step.overrides); const outputButtons = new Set(step.output_buttons); @@ -2515,7 +2592,7 @@ function renderMacroSteps() { ${macroNumber(index, "triggers.right", step.triggers.right, 0, 65535, "Right trigger", !overrides.has("right_trigger"))}
- Fields this step overrides + Outputs controlled by this step
${state.schema.macro_overrides.map((name) => `