diff --git a/.editorconfig b/.editorconfig index 1633b09a0..f33f720bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,8 @@ insert_final_newline = true [*.{java,kt,kts,gradle}] indent_style = tab -tab_width = 4 \ No newline at end of file +tab_width = 4 + +[*.{ts,js,css}] +indent_style = tab +tab_width = 2 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6adf006c..2d42fbb88 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Gradle CI +name: Build and Test on: push: @@ -10,7 +10,7 @@ on: - '.gitignore' - '.editorconfig' - 'CHANGELOG.md' - - 'docs' + - 'docs/**' pull_request: permissions: diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 000000000..d9edc04e6 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,72 @@ +name: Publish Docs +on: + push: + branches: + - 'main' + paths: + - 'docs/**' + - .github/workflows/publish-docs.yml + pull_request: + paths: + - 'docs/**' + - .github/workflows/publish-docs.yml + +permissions: + contents: read + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./docs + + if: > + github.event_name == 'push' || + github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v7 + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + - name: Deploy Production Docs + if: github.event_name == 'push' + run: bun wrangler deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + - name: Deploy Preview Docs + if: github.event_name == 'pull_request' + run: bun wrangler versions upload --preview-alias pr-${{ github.event.pull_request.number }} + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + + - name: Find preview comment + if: success() && github.event_name == 'pull_request' + id: find-preview-comment + uses: peter-evans/find-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Comment preview URL + if: success() && github.event_name == 'pull_request' + uses: peter-evans/create-or-update-comment@v5 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-preview-comment.outputs.comment-id }} + edit-mode: replace + body: | + + ## πŸ“– Documentation preview + + Preview: https://pr-${{ github.event.pull_request.number }}-controlify.isxander.workers.dev + + Updated for `${{ github.event.pull_request.head.sha }}` diff --git a/.github/workflows/publish.yml b/.github/workflows/publish-mod.yml similarity index 87% rename from .github/workflows/publish.yml rename to .github/workflows/publish-mod.yml index 27ce7677d..f5e92c499 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish-mod.yml @@ -1,4 +1,4 @@ -name: Release +name: Publish Mod on: [workflow_dispatch] permissions: @@ -67,3 +67,12 @@ jobs: git tag --annotate "$VERSION" --message "Release $VERSION" git push origin "refs/tags/$VERSION" + + - name: Write changelog to docs + run: ./gradlew writeChangelogToDocs + + - name: Commit and push changelog + uses: stefanzweifel/git-auto-commit-action@v7 + with: + file_pattern: 'docs/content/changelog/*' + commit_message: "chore: add changelog" diff --git a/.github/README.md b/README.md similarity index 98% rename from .github/README.md rename to README.md index 0e4c9afe6..dbc262e57 100644 --- a/.github/README.md +++ b/README.md @@ -27,7 +27,7 @@ Controlify Splitscreen is a **separate mod in development** that adds splitscree ## [Wiki](https://moddedmc.wiki/project/controlify) -Read up on the [Controlify Wiki](https://moddedmc.wiki/project/controlify) for more information on how to use Controlify, how to configure it, and how to develop for it. +Read up on the [Controlify Wiki](https://controlify.isxander.dev) for more information on how to use Controlify, how to configure it, and how to develop for it. ## What is Controlify? diff --git a/TESTING_REPORT.md b/TESTING_REPORT.md deleted file mode 100644 index 39ceaed0c..000000000 --- a/TESTING_REPORT.md +++ /dev/null @@ -1,685 +0,0 @@ -# Controlify testing report - -## Executive summary - -Controlify now has the beginnings of a valuable end-to-end test harness: a real Minecraft client can discover an SDL virtual controller, receive controller input, exercise gameplay, and capture effects sent back to the device. This is the right foundation for testing the behavior that is most likely to regress at integration boundaries. - -The recommended strategy is not to put every test into the client game-test suite. Controlify has four useful test layers: - -1. **JVM tests** for codecs, mappings, maths, rumble composition, migrations, and other deterministic logic. -2. **Client game tests** for controller discovery, input polling, bindings, gameplay, screens, and device output. -3. **Resource-pack integration tests** for reload listeners, pack priority, cache invalidation, and the visible behavior produced by loaded data. -4. **Environment and compatibility tests** for loader parity, optional mods, platform integrations, and physical hardware. - -The highest-value next step is to make the virtual controller context expose the corresponding Controlify `ControllerEntity` and provide tick-synchronised input and output assertions. With that in place, controller discovery, mappings, bindings, movement, gyro, resource reloads, and most gameplay actions become straightforward to test. - -The first implementation milestone should cover the following suites: - -- Virtual controller lifecycle and capabilities -- SDL-to-Controlify input translation -- Binding transitions and default binds -- Deadzones and controller mappings -- Input-mode and active-controller switching -- Analogue movement and core gameplay actions -- Rumble manager behavior and the remaining rumble event sources -- Pure JVM coverage for mappings, codecs, config migrations, and DualSense effect encoding - -These tests exercise central contracts used by almost every Controlify feature and provide better value than starting with screenshots or optional-mod compatibility. - -## Current game-test framework - -### What is already possible - -The framework under `src/gametest` can currently: - -- Attach SDL virtual gamepads identified as Xbox or DualSense controllers. -- Define available SDL buttons and axes or provide a custom SDL gamepad mapping. -- Advertise gyro sensors and touchpads. -- Press and release buttons, move axes, and submit gyro or generic sensor samples. -- Wait for Minecraft ticks and use Fabric's singleplayer world, screen, input, and screenshot helpers. -- Observe simple rumble, trigger rumble, player index, RGB LED output, sensor enablement, and raw device-effect packets. -- Decode raw effect packets through a pluggable `GamepadEffectDriver`. -- Exclude physical controllers from the test environment while allowing virtual controllers. - -`RumbleTests` proves the complete path from a world event, through Controlify's rumble logic and SDL driver, to a virtual device callback. That pattern can be reused for inputs, gyro, adaptive triggers, LEDs, and server-originated vibration. - -### Important limitations - -| Limitation | Why it matters | Recommended framework change | -| --- | --- | --- | -| The test controller does not expose its `ControllerEntity`. | Tests cannot inspect identity, components, settings, raw state, mapped state, bindings, or the active-controller relationship. | Resolve the entity after attachment by SDL joystick ID or UID and expose it from `TestControllerContext`. | -| Input mutation and tick advancement are separate operations. | Tests can accidentally assert before SDL and Controlify have both polled the new state. | Add `pressAndTick`, `releaseAndTick`, `setAxisAndTick`, and lower-level `awaitInputState` helpers. Keep non-ticking methods for multi-input frames. | -| Hats, balls, and touchpad fingers cannot be injected. | Generic joystick hats, touchpad state, and all mapping conversions cannot be tested end to end. | Wrap `SDL_SetJoystickVirtualHat`, `SDL_SetJoystickVirtualBall`, and `SDL_SetJoystickVirtualTouchpad`. | -| SDL setter return values are ignored. | Invalid indices or failed injections silently become timeouts later. | Check every SDL call and throw an `SDLException` at the mutation site. | -| Output state only stores the latest value. | A stale output can satisfy a later test, and pulse duration, ordering, mixing, and reset behavior cannot be asserted. | Record immutable output events with a sequence number and client tick; provide `clearOutput`, `outputHistory`, and `waitForNextOutput`. | -| Raw effect buffers are retained. | The native callback's `ByteBuffer` lifetime and position are not guaranteed after the callback. | Copy remaining bytes immediately and expose an immutable byte array or read-only copied buffer. | -| Controller cleanup only detaches the SDL joystick. | Tests need to know that Controlify processed disconnection and selected a fallback controller/input mode. | Close the opened handle as appropriate, detach, and wait until the manager no longer contains the entity. | -| Configuration is global and persistent across scenarios. | One test can change bindings, sensitivity, profiles, or feature flags for every later test. | Add a fixture that snapshots relevant DTOs/settings, applies changes on the client thread, and restores them in `close()`. | -| No resource-pack fixture exists. | Reload listener behavior and pack priority cannot be tested through Minecraft's real resource manager. | Register packaged test packs during test-mod client startup and add enable/reload/restore helpers. | -| Screen state is mostly opaque. | GUI tests need reliable assertions for focus, hovered slot, edit-box content, cursor location, and open overlays. | Add test-only accessors and focused semantic helpers rather than relying primarily on pixels. | -| Device power cannot be controlled through the virtual descriptor. | Low-battery notification behavior cannot be triggered deterministically. | Add a test-only power-state seam at the SDL driver boundary or test the notifier with a fake entity/component. | -| HD haptics require an eligible audio device. | A virtual SDL controller alone cannot exercise audio-device discovery and playback. | Unit-test audio conversion/queue logic and use an injectable haptic sink; leave native playback as a platform smoke test. | - -### Test isolation requirements - -Every client game-test class should leave the client in the same state in which it found it. A common test fixture should restore: - -- Attached virtual controllers and current-controller selection -- Input mode -- Open screen and virtual-mouse state -- Global, profile, and device settings -- Bound inputs and radial actions -- Server policies -- Enabled resource packs -- Spawned entities, inventory contents, game mode, camera, HUD, and player pose when changed -- Outstanding rumble or trigger effects - -Prefer one behavior per test entrypoint where Fabric permits it. If several scenarios must share a world or controller, clear input and output state before each scenario and wrap failures with the scenario name, as `RumbleTests` currently does. - -## Recommended test backlog - -Priorities are based on regression impact, breadth of exercised code, determinism, and implementation cost: - -- **P0:** foundational coverage to implement first. -- **P1:** important feature coverage after the foundation exists. -- **P2:** valuable, but more specialised or more expensive to maintain. -- **P3:** environment-dependent coverage that should not block ordinary CI. - -### P0: controller lifecycle and input foundations - -#### Virtual controller lifecycle tests - -| Scenario | Assertions | Blockers | -| --- | --- | --- | -| Attach an Xbox virtual controller. | Exactly one new controller appears; it becomes current; driver name, VID/PID, UID, GUID, and Xbox controller namespace are as expected; an input component exists. | Expose the attached `ControllerEntity` and manager lookup. | -| Attach a DualSense virtual controller. | The controller is identified as DualSense and receives the expected DualSense, input, rumble, LED/effect, gyro, and touchpad components according to advertised capabilities. | Entity/component inspection and a decoded effect driver. | -| Detach the current controller. | The manager removes it, a disconnect event fires once, its resources close, and the current controller/input mode fall back correctly. | Lifecycle event capture and close-and-await helper. | -| Attach two virtual controllers. | UIDs are distinct and stable; input from the inactive controller selects it only under the documented profile/current-controller rules. | Multiple-controller fixtures and active-controller assertion. | -| Attach an unsupported or non-gamepad virtual joystick. | The SDL joystick driver is selected, counts are correct, and no gamepad-only components or mappings appear. | Builder support for joystick type, hats, and generic input inspection. | -| Advertise optional output callbacks selectively. | Rumble, trigger-rumble, and LED components are present only when SDL reports those capabilities. | Builder switches for selectively omitting callbacks. | - -Also assert event API behavior: `CONTROLLER_CONNECTED`, `CONTROLLER_DISCONNECTED`, and state-update events should identify the correct entity, contain the correct hotplug flag, and not be duplicated during reinitialisation. - -#### SDL input translation tests - -These tests should observe both `InputComponent.rawStateNow()` and the deadzoned/mapped `stateNow()`. - -| Scenario | Assertions | Blockers | -| --- | --- | --- | -| Every advertised SDL gamepad button | Pressing one SDL index activates only the corresponding `GamepadInputs` identifier; release advances `stateThen`/`stateNow` correctly. | Entity and input-state access. | -| Signed stick axes | Negative and positive SDL values map to the correct directional axes, with the opposite direction at zero. Check endpoints and representative partial values. | Tick-aware axis helpers and approximate assertions. | -| Trigger axes | Resting, half, and fully pressed values map to the expected `0..1` Controlify axis. | Input-state access. | -| Generic joystick buttons and axes | Each button maps by index; each signed axis splits into positive and negative `JoystickInputs`; asymmetric `short` endpoints still map to `-1` and `1`. | Generic joystick builder preset. | -| Generic joystick hats | All nine SDL hat states map to the corresponding `HatState`. | Hat injection helper. | -| Custom SDL gamepad mapping | A deliberately unusual physical layout is transformed into canonical SDL gamepad inputs before Controlify reads it. | A reusable mapping fixture and state inspection. | -| Unadvertised inputs | Inputs absent from the descriptor mask do not appear as supported or accidentally activate. | Ability to query supported identifiers/counts. | - -#### Binding state and context tests - -Test `InputBinding` as a public behavioral contract, not its ring-buffer implementation. - -- Button binding: `digitalNow`, `digitalThen`, `justPressed`, `justReleased`, and `analogueNow` across press, hold, and release frames. -- Axis binding around `buttonActivationThreshold`, including equality and values just below/above it. -- `justTapped` for a short press and non-tap behavior for a held press. -- Directional axes and opposing inputs, including simultaneous opposites. -- Suppression and restoration without manufacturing false transitions. -- Allowed `BindContext` behavior in-game, in normal GUI navigation, virtual mouse, radial menu, and no applicable context. -- GUI press repeat timing and reset after navigation. -- Borrowed state-access outputs and fake presses if these remain supported API behavior. -- Key-mapping emulation: controller press and release update the correlated Minecraft key mapping exactly once. -- Automatically generated bindings for otherwise uncorrelated Minecraft key mappings. - -This suite needs binding lookup by ID and deterministic binding overrides in the config fixture. - -#### Deadzone and controller-mapping tests - -Use JVM tests for exhaustive transformations and a smaller client test to prove the settings-to-driver integration. - -JVM coverage should include: - -- Radial deadzone behavior at zero, just below, exactly at, just above, and at full magnitude. -- Default deadzone versus group-specific overrides. -- Every `MappingEntry` combination: button-to-button/axis/hat, axis-to-button/axis/hat, hat-to-button/axis/hat, and nothing-to-button/axis/hat. -- Inversion, thresholds, min/max remapping, all hat directions, and missing source inputs. -- Multiple mappings targeting the same output, preserving the documented order/overwrite behavior. -- Mapping and deadzone codec round trips, typed and fuzzy forms, malformed entries, and unknown types. - -Client coverage should: - -- Install a mapping in the virtual controller's device settings. -- Inject a raw input and assert raw, mapped, and deadzoned state independently. -- Change the mapping and deadzone at runtime and prove the next poll uses the new values. -- Reload or replace mapping data and verify cached mappings do not remain stale. - -The last scenario is a high-value regression test because `ControllerMappingStorage` currently owns a static cache and is not itself a reload listener. - -#### Input-mode and active-controller tests - -- Controller input switches `KEYBOARD_MOUSE` to `CONTROLLER` when mixed input is disabled. -- Controller input switches to `MIXED` when mixed input is enabled. -- The frame that causes a mode switch is not also treated as an action press. -- Keyboard or mouse use switches back according to current settings. -- Tiny resting noise inside the configured deadzone does not switch modes. -- Input is suppressed when the window is inactive and out-of-focus input is disabled. -- A non-current controller can become current only when the current controller is idle and the active profile does not pin a UID. -- Repeated faulty input eventually disables the offending controller through the intended safeguard. - -These tests need helpers to inspect and restore current controller, input mode, focus state, and active profile. - -### P0: gameplay input - -#### Movement tests - -Create a flat singleplayer world and compare player input vectors and server-observed movement. - -- Full and partial forward/back/left/right movement preserve analogue magnitude. -- Diagonal input with length greater than one is limited, not normalised upward from smaller values. -- `alwaysKeyboardMovement` and server analogue-movement policy quantise values at `buttonActivationThreshold`. -- Server whitelist behavior selects analogue or keyboard-style movement for matching and non-matching addresses; the pure address matching can be unit-tested. -- Opening a screen clears controller movement and action key states. -- Jump does not fire merely because the button was held while closing a GUI. -- Toggle sneak versus hold sneak, including flying, swimming, riding, landing, and dismount reset cases. -- Sprint, auto-jump, and disable-fly-drifting behavior under their settings and server policies. -- `DualInput` combines or chooses keyboard/controller state as intended in mixed-input configurations. - -Prefer assertions on `ClientInput` and player state over position-only assertions; position introduces physics and network timing noise. - -#### Look and gyro tests - -- Stick look applies horizontal/vertical sensitivity, inversion, configured input curve, and pitch clamping. -- Reduced aiming sensitivity applies only in the intended aiming/use contexts. -- Registered `LookInputModifier`s run in order and can adjust or cancel input according to the API contract. -- A gyro-capable virtual controller causes SDL sensor enablement and creates a `GyroComponent`. -- Submitted gyro samples update all three axes with the expected orientation/sign. -- Gyro button modes, relative mode, pitch/yaw inversion, yaw mode, and sensitivity alter camera rotation correctly. -- Gyro input is suppressed in keyboard mode, unfocused state, or disabled settings. -- Flick-stick produces the intended yaw animation and does not leave a residual turn. - -Exact camera deltas should use tolerances and controlled settings. The test fixture should disable unrelated easing and restore the original camera orientation. - -#### Core action tests - -Use default binds first, then one remapped-bind case to prove actions consume bindings rather than hardcoded SDL indices. - -- Open/close inventory and pause screens. -- Next/previous hotbar slot, including wraparound and hold-repeat cadence. -- Drop one item, repeated drop, and drop full stack. -- Swap main/offhand items. -- Change perspective and restore renderer state. -- Pick block and pick block with NBT under the correct permissions. -- Toggle HUD and player list. -- Open the general, game-mode, hotbar selection, and creative load/save radial menus only when allowed. - -Debug actions and screenshots are lower priority; test the binding/action dispatch with one harmless representative rather than making CI mutate debug or screenshot state repeatedly. - -### P0: deterministic JVM tests - -These tests should run without starting Minecraft wherever the involved classes permit it. - -#### Input and utility maths - -- Linear, power, cubic-blend, and S-curve input transformations at `-1`, intermediate negatives, zero, intermediate positives, and `1`. -- Sign preservation, endpoint preservation, and monotonicity for supported curve settings. -- Short/float conversion endpoints and round-trip tolerance. -- Vector easing and deadzone functions, including zero-length vectors and diagonal values. -- Hold-repeat timing and reset behavior. -- Ring-buffer growth/history behavior used by bindings. -- Snap-point directional ranking with aligned, diagonal, tied, wrapped, and out-of-range points. - -#### Rumble logic - -- `RumbleState` integer packing/unpacking endpoints and representative values. -- Basic effect constant frames, `byTick`, `byTime`, join, sequence, repeat, early finish, and priority. -- Continuous effect timeout, minimum duration, stop condition, silent frames, and builder modifiers. -- `RumbleManager` combines concurrent effects by maximum motor magnitude, applies source and master strengths, removes finished effects, sends a final zero state, clears effects, and handles silent mode. -- Trigger-rumble state scaling and driver conversion to unsigned 16-bit motor values. - -#### DualSense effects - -- Every `DualsenseTriggerEffect` variant validates parameter boundaries. -- Zero strength/amplitude produces the off state where documented. -- Feedback, weapon, vibration, multi-position, and slope effects produce exact type and parameter bytes. -- `DualsenseEffectsState` writes exactly 47 bytes with correct offsets, enable flags, LEDs, mute light, and left/right trigger placement. -- `TriggerEffectCodecs` accepts every documented compact and expanded form, round-trips values, and rejects invalid or ambiguous objects. - -These tests are especially valuable because byte-layout mistakes can be unsafe or impossible to diagnose without a physical controller. - -#### Configuration and data fixing - -- DTO/settings round trips for global, shared, device, profile, input, gyro, rumble, DualSense, Bluetooth, and HD-haptic configuration. -- Collection copying: mutating a settings object should not unexpectedly mutate the source DTO, and serialised DTO collections should not alias mutable settings collections. -- Default values and bounds such as non-negative preferred profile. -- One golden old-config fixture for each source schema and one focused fixture for each fix: - - The large controller/profile/device migration - - Horizontal look inversion - - Radial action migration and deduplication - - Analogue movement whitelist and seen servers - - DualSense trigger-effects setting - - Guide GUI scales - - Generated binding IDs in bindings and radial actions -- Full migration from the oldest supported fixture to the current schema followed by a successful current codec parse. -- Unknown fields survive migrations where forward-compatible remainder behavior is intended. - -Keep migration fixtures as readable JSON resources. Assert the complete canonical output for focused fixtures so accidental field loss is visible. - -#### Codecs and network payloads - -- Input, rule, guide, radial icon, controller type, HID ID, deadzone, and mapping codec round trips. -- Strict/fuzzy codec ambiguity and malformed-object errors. -- Vibration, origin vibration, entity vibration, and server-policy packet round trips. -- Empty, one-frame, and long vibration arrays, plus invalid lengths if packet decoding is expected to defend against them. -- Unknown rumble-source and server-policy identifiers follow their documented registration/ignore behavior. -- Handshake equal, older-client, and newer-client protocol outcomes. - -### P1: expanded rumble integration - -The existing suite covers lightning, explosions, outgoing damage, and incoming damage. Add scenarios for every other event family represented by the rumble mixins: - -| Feature | Suggested scenario | Main assertion | -| --- | --- | --- | -| Block breaking | Break a controlled block at known progress/speed. | Rumble begins, scales with progress if intended, and stops on completion/cancel. | -| Fishing | Cast, hook an entity/item, and reel in. | The correct interaction pulse occurs once per event. | -| Item use | Eat/drink, draw a bow, charge a crossbow, and use a continuous item. | Event-specific frame pattern and cleanup after release/completion. | -| Item break | Consume the final durability point. | A distinct item-break pulse occurs once. | -| Slow block movement | Walk through a representative slowing block. | Continuous effect exists only while the condition holds. | -| Water landing | Fall into water at two controlled velocities. | Stronger landing produces no weaker output and the effect ends. | -| Level events | Trigger representative world level events wired to rumble. | Expected motor/channel activates without unrelated duplicates. | - -Add manager-level client tests for: - -- Master and per-source intensity settings. -- Rumble disabled globally for the profile. -- Concurrent event mixing and priority. -- Paused client, keyboard input mode, inactive window, and config-screen exceptions. -- Switching current controller while an effect is active. -- Output history returning to zero after every effect. - -Use unsigned comparisons for captured `short` values, since SDL motor magnitudes use unsigned 16-bit values represented by Java `short`. - -### P1: DualSense, trigger rumble, LEDs, and sensors - -#### Adaptive triggers - -- Equipping or using an item matching built-in use/swing rules sends the expected left/right trigger effects. -- Releasing use or changing held item sends an off effect. -- Profile setting disables trigger effects. -- Controller input suppression clears effects. -- Resource rules override Java component and item registrations in the documented priority order. -- Component registrations override item registrations; a `null` component result falls through. -- Cached matches update after an `ItemStack` component/count change. -- Reload and client-tag updates invalidate resolved rules and stack caches. -- Registry/tag placeholders resolve after entering a world and do not leak across worlds. - -Implement a `DualsenseEffectDriver` in the test framework that copies and decodes the 47-byte state. Tests should assert semantic fields and reserve raw-byte assertions for the JVM layout suite. - -#### Other device output - -- Queue trigger rumble and verify exact left/right output plus final zero state. -- Set player index and verify callback propagation. -- Set RGB LED color, including settings-driven brightness/color if applicable. -- Change the DualSense mute light and verify effect packets. -- Assert that unsupported virtual controllers do not expose or attempt unsupported output. -- Verify output callback failures are logged/handled without corrupting later controller updates; this needs a builder option for callbacks that return `false`. - -#### Touchpads - -- Advertised touchpad and finger counts create the correct `TouchpadComponent` model. -- Finger down/move/up updates ID, position, pressure, and current/previous frames. -- Two touchpads and multiple simultaneous fingers remain independent. -- Touchpad click maps to the canonical touchpad button. - -Virtual-mouse touch movement is currently commented out. Keep component/driver tests active now, and add cursor tests when that feature is re-enabled rather than encoding the disabled implementation as expected behavior. - -### P1: resource-pack integration - -#### Required resource-pack fixture - -Add a test-mod client initializer that registers one or more built-in test packs before the initial resource reload. The fixture should: - -1. Snapshot selected pack IDs. -2. Enable a named test pack at a known priority. -3. invoke `Minecraft.reloadResourcePacks()` on the client thread. -4. Wait for the returned reload future and one settling tick. -5. Run semantic assertions against the relevant manager and visible controller behavior. -6. Restore the original selection and reload in `close()` even after failure. - -Use small packs with unique `controlify_test:*` identifiers. Include separate low- and high-priority layers where precedence is under test. Avoid writing packs into the user's resource-pack directory during a run. - -#### Resource-driven feature matrix - -| Data family | Essential tests | Observable result | -| --- | --- | --- | -| Controller identification | Parse JSON5; recognise test VID/PID; duplicate HID priority; `dont_load`; friendly name, namespace, mapping ID, and icon; live type change. | Attached controller type or deliberate absence; controller reinitialises once on reload. | -| Default binds | Default fallback; controller-specific layer; `clear_below`; malformed layer; reload with `keepDefaultBindings` true/false. | Binding's default/current input matches the documented layering rules. | -| Default profile config | Recursive object merge, scalar/list replacement, multiple layers, malformed layer, missing base resource. | New profile/controller settings contain the merged values or fail clearly when the required base is missing. | -| Input font mappings | Known input, unknown fallback character, namespace fallback, malformed character, and controller-type switch. | Produced component uses the expected font and glyph character. | -| Keyboard layouts | Requested locale, default-language fallback, missing layout fallback, malformed path/JSON, and all built-in layout shapes. | Resolved `KeyboardLayout` and an on-screen keyboard with expected keys/actions. | -| Guide rules | Conditions, forbidden facts, verbosity, stacking, duplicate locations, dynamic rules, pack priority, and `replace`. | `GuideInstance` resolves the expected action/location set and refreshes after reload epoch changes. | -| Radial icons | Model and texture definitions, per-binding merge, high-pack override, malformed entry/layer, explicit and generated binding candidates. | Candidate status and resolved icon match the final merged map. | -| Adaptive trigger rules | Use/swing separation, item/tag/component predicates, first-match order, pack priority, malformed layer, reload/cache invalidation. | Matching held items produce the expected semantic trigger effect. | -| Controller mappings | All mapping forms, deadzone definitions, missing file, malformed file, and reload. | Raw virtual input is transformed to the expected Controlify state without stale cache data. | -| SDL gamepad database | A unique test GUID mapping and override order. | Virtual joystick is treated as a gamepad with the expected canonical controls. | - -Pack-priority tests deserve special attention. Several loaders intentionally use resource stacks in different directions, and guide `replace` behavior, default-bind prepending, default-config merging, radial-icon overwrite order, and adaptive-trigger reversed layers should each have an explicit two-pack test. - -### P1: server integration and policies - -Use Fabric's singleplayer/integrated server for command and packet flow, and pure codec tests for malformed payloads. - -- Successful Controlify handshake sets analogue movement to allowed. -- Protocol mismatch disconnects with the correct client/server-old distinction. -- Disconnect resets all `ServerPolicies` to `UNSET`. -- Policy packets update reach-around, fly drifting, and analogue movement; unknown IDs are ignored. -- `/vibratecontroller ... static` reaches the virtual controller for the requested duration and strengths. -- Positioned vibration attenuates with distance and stops outside range. -- Entity vibration follows a moving entity, handles entity removal, and attenuates by distance. -- `allowServerRumble = false` blocks all three client-bound vibration forms. -- Commands require the intended permission level and reject invalid strength, range, and duration arguments. - -Add packet/output history before these tests so duration and attenuation can be asserted without sampling races. - -### P1: reach-around and gameplay patches - -#### Reach-around - -Cover the decision table directly and through one placement integration test: - -- Existing block/entity hit is never replaced. -- Miss while looking down, on ground, supported by a non-air block produces a hit on the supporting block. -- Pitch below the threshold, airborne player, air support, vehicle, or disallowed policy preserves the miss. -- `OFF`, singleplayer-only/default behavior, `ALLOWED`, `DISALLOWED`, and `UNSET` combinations respect global setting and server policy. -- Interacting with the generated hit can place a block without breaking ordinary block interaction. - -The pure hit-result test can call `ReachAroundHandler` with controlled entities/world state; the placement test proves the Minecraft mixin path. - -#### Other gameplay patches - -- Analogue boat input produces proportional steering/acceleration and keyboard input still works in mixed mode. -- Tutorial/key-message substitutions use controller glyphs only in controller modes. -- Accessibility behavior such as auto-jump and sprint handling follows profile settings. -- Offhand and vehicle transitions do not leave stale held/toggled actions. - -### P1: public API and extension contracts - -Controlify's extension APIs deserve small contract tests so internal refactors do not silently break other mods: - -- Entrypoint ordering: pre-init registration runs before guide domains and bindings freeze; init runs after core systems exist; controller discovery callback runs once after discovery. -- A failing third-party entrypoint is logged and isolated rather than preventing other entrypoints or Controlify from initialising. -- Binding registration validates duplicate IDs, controller filters, context sets, radial-candidate metadata, key emulation, and `on`/`onOrNull` behavior. -- Binding registration is rejected after the registry is locked. -- Custom guide domains accept facts and dynamic rules before freezing, reject duplicate/late registration, participate in resource reload, and create usable instances. -- Rumble API calls route to the current capable controller and become a safe no-op when no capable current controller exists. -- Trigger-effect API registrations obey resource, component, and item priority and handle a component callback returning no effect. -- Look-input modifiers and active-controller/state events execute in documented order and receive the correct controller. -- Custom screen processors/component processors are selected by the registry and can define focus, navigation, guides, and virtual-mouse behavior. -- Public virtual-mouse snapping points contributed by a screen are collected, filtered, and navigated with built-in points. - -Use a tiny test-only Controlify entrypoint in the game-test mod for lifecycle and registration tests. Keep deliberately failing registrations isolated to a launch profile or test phase so global frozen registries cannot contaminate later scenarios. - -### P2: screen operations and virtual mouse - -GUI tests should favour semantic state over screenshots. Screenshots are best reserved for a few stable visual contracts such as guide placement, controller glyphs, keyboard layout, and radial selection. - -#### Generic navigation - -- D-pad/stick navigation moves focus among vanilla buttons in all four directions. -- Confirm activates the focused component once; back closes to the correct parent. -- Hold repeat works for lists, sliders, and cycling controls without skipping unexpectedly. -- Slider left/right changes values by expected increments and respects endpoints. -- Tab navigation, list entries, and scroll position remain visible after focus changes. -- UI focus/clack sounds respect `extraUiSounds` and do not duplicate. -- Mouse use, controller use, and mixed input update focus and cursor visibility consistently. - -#### Screen-specific processors - -Create representative tests for: - -- Title, pause, options, and options-subscreen navigation. -- World selection and creation. -- Multiplayer server list, direct join, add/edit server, and server-list entry actions. -- Language selection. -- Chat, command suggestions, and history. -- Sign editing and line switching. -- Inventory/container slot navigation, quick move, take/place one/all, outside click, and close. -- Recipe-book tabs, filter, recipes, and page navigation. -- Creative inventory tabs and search. -- Merchant, bundle, loom, stonecutter, and enchantment special actions/snapping. - -Start with inventory, options, and chat because they exercise most shared processors. Add one regression test per specialised processor rather than duplicating the entire generic navigation matrix on every screen. - -#### Virtual mouse - -- Toggle only on eligible screens and restore ordinary cursor behavior when disabled. -- Stick movement applies sensitivity, easing, window scaling, and bounds. -- Releasing movement snaps to the nearest in-range point. -- Directional snapping chooses the closest valid point and handles wrap/fallback behavior. -- Left, right, and shift click deliver correct mouse button/modifier sequences. -- Scroll accumulates and decays without changing direction or continuing indefinitely. -- Back closes the screen. -- Container/recipe/enchantment/loom/stonecutter snap-point providers expose visible, valid targets. - -Expose virtual-mouse target/current coordinates and last snapped point through test-only accessors. Avoid asserting GLFW cursor position alone because interpolation makes it timing-sensitive. - -### P2: radial menus, guides, glyphs, and keyboard - -#### Radial menus - -- Open on press, select with stick or directional bindings, execute on release/confirm, and cancel cleanly. -- Deadzone and focus timeout prevent accidental selection. -- Page switching preserves or resets selection according to the UI contract. -- Missing/unbound actions render safely and do not execute another action. -- User-configured radial actions and generated modded bindings appear in the expected order. -- Game-mode and creative hotbar menus enforce permissions/mode restrictions. -- Model, texture, and fallback radial icons render without missing assets. - -#### Button guides - -- Unit-test every built-in fact provider with controlled contexts, especially verbosity thresholds, player mode/pose, hit result, held items, and container cursor/item state. -- Test rule selection, forbidden facts, stacking, action-location conflicts, and missing facts. -- Verify `GuideInstance` recomputes after a domain reload epoch and after relevant context changes. -- Use screenshots for one in-game guide at top/bottom and one container guide, at fixed resolution and GUI scale. -- Assert guide enablement, verbosity, and scale settings semantically before visual comparison. - -#### Input glyphs - -- Each built-in controller namespace maps canonical inputs to a non-fallback glyph where defined. -- Unknown inputs use the namespace's unknown glyph. -- Missing controller namespace falls back to the default map. -- A binding with multiple relevant inputs produces the expected component sequence/font. -- Controller type changes update glyph output after reload/reinitialisation. -- Translation/keybind mixins substitute controller glyphs only in the intended input mode. - -#### On-screen keyboard - -- Full, simple, and server-IP layouts resolve and render. -- Character, backspace, delete, cursor, shift/caps, space, enter, and cancel keys mutate the target correctly. -- Shift/caps state changes displayed and inserted characters. -- Locale fallback selects `en_us` when a locale-specific file is absent. -- Chat, sign, server-address, and ordinary edit-box integrations choose the right layout and hints. -- Disabling the on-screen keyboard setting prevents automatic opening without breaking edit-box focus. - -### P2: configuration and settings UI - -- Load missing configuration using defaults and save a current-schema file. -- Corrupt configuration produces the intended recovery/error behavior without partially applying state. -- Profiles and device settings are created, selected, renamed/updated, and associated with stable controller UIDs. -- Preferred profile and pinned controller survive restart/reload semantics. -- Applying settings updates live components; cancel/discard restores previous values. -- Reset-to-default uses the currently loaded resource-pack defaults. -- Rebinding detects intended buttons/axes, supports cancellation and unbinding, and does not capture resting noise. -- Deadzone calibration and gyro calibration update only the target controller/device settings. -- Rumble preview and LED/trigger settings produce output only on capable controllers. -- A newly attached generic joystick without a mapping opens the mapping setup stage; completing or cancelling it advances to the correct next screen. -- A Bluetooth DualSense shows its warning only when applicable, persists the dismissal setting, and preserves the screen that was open before the wizard. -- Multiple queued setup wizards run in attachment order and skip disabled stages without trapping the current screen. -- Hotplug connection toasts contain the resolved friendly controller name and do not appear for initial discovery where the contract excludes them. - -Client restart persistence is expensive in a game test. Cover serialization and manager load/save in JVM or focused integration tests, then use one process-level smoke test for persistence if the CI runner can launch a second client instance with an isolated game directory. - -### P3: constrained and environment-dependent coverage - -| Feature | Practical automated coverage | Remaining blocker | -| --- | --- | --- | -| Low-battery notifier | Unit/integration test with an injected `BatteryLevelComponent`; verify threshold, deduplication, recovery, setting, and toast content. | SDL virtual joystick does not provide a controllable power callback. | -| HD haptics | Test sound decoding, channel conversion, queue length, timeout, and sink calls with a fake audio sink. | Native path requires a discoverable four-channel DualSense audio device. | -| Steam Deck | Unit-test mode/CEF payload parsing and driver decision logic; run an opt-in Deck smoke suite. | Requires SteamOS/Decky/CEF environment and possibly hardware. | -| SDL native loading | Process-level tests for native-source precedence, missing loader/native failure, subsystem initialisation, audio failure tolerance, configured hints, and native/binding version mismatch. | Native libraries cannot be unloaded and safely reloaded with different conditions in one JVM; use isolated launch profiles. | -| Native keyboard | Test decision/fallback logic and ordinary overlay keyboard. | Platform modal behavior needs OS/controller-specific automation. | -| Physical SDL backends | Run opt-in smoke tests for representative Xbox, PlayStation, Switch, and generic devices. | Hardware lab and stable USB/Bluetooth provisioning. | -| Optional mods | Launch dedicated compatibility profiles and test one representative screen/action per integration. | Additional mod dependencies, version churn, and licensing/distribution constraints. | -| NeoForge parity | Compile all common tests and build a loader-neutral harness where possible; run manual/smoke matrix initially. | Current client game-test framework is Fabric-specific. | - -Optional-mod priorities should be YACL first because it backs Controlify's own settings, then Sodium/Reese's Sodium Options, Simple Voice Chat, Iris, and FancyMenu. Compatibility tests should verify that controller navigation works and no mixin/accessor failure occurs; they should not duplicate those mods' own feature tests. - -## Framework implementation roadmap - -### Milestone 1: controller observability and deterministic frames - -1. Resolve and store the Controlify `ControllerEntity` created for each virtual joystick. -2. Expose identity, components, raw/mapped state, bindings, settings, and whether it is current. -3. Add checked input methods for buttons, axes, hats, touchpads, and sensors. -4. Add an explicit frame API: - - Mutate several inputs without ticking. - - Commit one frame and wait for Controlify to poll it. - - Await a semantic input/binding condition with a bounded timeout. - - Neutralise all inputs during cleanup. -5. Wait for both attachment and detachment lifecycle completion. - -This milestone unlocks nearly every P0 client test. - -### Milestone 2: output event recording - -1. Replace latest-only fields with immutable event records while retaining convenient latest-value accessors. -2. Record sequence, client tick, output kind, and copied payload. -3. Add clear, snapshot, and `waitForNext` APIs that cannot be satisfied by stale output. -4. Treat captured motor values as unsigned 16-bit values in assertion helpers. -5. Implement a semantic DualSense effect decoder. -6. Allow callback success/failure and supported capabilities to be configured independently. - -This milestone makes temporal rumble, server vibration, LEDs, trigger rumble, and adaptive-trigger tests reliable. - -### Milestone 3: state fixtures - -Add `AutoCloseable` fixtures owned by `ControlifyGameTestContext` for: - -- Global/profile/device settings -- Binding overrides and deadzones/mappings -- Input mode and current controller -- Server policies -- Player/world state -- Screens, cursor, and virtual mouse - -Fixtures must restore state in `finally`/`close`, even after an assertion fails. Prefer DTO copies or manager-supported snapshots over reflection-based field-by-field restoration. - -### Milestone 4: resource packs - -1. Add a client initializer to the test mod so test packs are registered before reload listeners freeze or initial resources load. -2. Package minimal base, lower-priority, and higher-priority test packs under game-test resources. -3. Implement pack selection, async reload waiting, and guaranteed restoration. -4. Add accessors for loaded manager state only where no public behavioral observation exists. -5. Run controller-type reload tests with care: they intentionally replace `ControllerEntity` instances, so test contexts must either follow reinitialisation or declare themselves invalid. - -### Milestone 5: GUI semantics - -1. Add accessors for focused widget, screen processor, virtual-mouse target/current position, snap points, hovered slot, edit-box contents, and guide/radial selection. -2. Build semantic helpers such as `pressBinding`, `assertFocused`, `assertScreen`, `assertSlot`, and `assertText`. -3. Fix window size, GUI scale, language, resource packs, and animation time for screenshot tests. -4. Keep screenshot templates few and intentional; use semantic assertions for most behavior. - -### Milestone 6: external seams and matrices - -- Introduce small injectable boundaries for battery readings and haptic output if production design permits them cleanly; otherwise use game-test mixins. -- Create separate Gradle/run profiles for optional mods rather than loading every compatibility dependency into core CI. -- Compile and run JVM tests on every supported Stonecutter version. -- Run the deterministic Fabric client suite on every supported version when runtime cost permits; otherwise run the full suite on one version and lifecycle/input/resource smoke tests on the others. -- Keep physical hardware and Steam Deck suites opt-in and non-blocking until a managed runner exists. - -## Suggested suite layout - -The exact class names can follow project conventions, but separating tests by responsibility will make failures easier to diagnose: - -```text -src/test/java/.../controlify/test/ - binding/ - codec/ - config/ - controller/mapping/ - dualsense/ - rumble/ - server/packet/ - util/ - -src/test/resources/ - config_migrations/ - codec_fixtures/ - -src/gametest/java/.../controlify/gametest/tests/ - ControllerLifecycleTests - ControllerInputTests - BindingTests - MovementTests - GameplayActionTests - GyroTests - RumbleTests - DualsenseOutputTests - ResourcePackTests - ServerIntegrationTests - ScreenNavigationTests - VirtualMouseTests - RadialMenuTests - GuideAndGlyphTests - KeyboardTests - -src/gametest/resources/ - resourcepacks/controlify_test_base/ - resourcepacks/controlify_test_low/ - resourcepacks/controlify_test_high/ - templates/ -``` - -Do not create one game-test entrypoint for every tiny assertion if client startup dominates runtime. Group related scenarios when they can safely share expensive setup, but reset controller input, output history, player state, and feature settings between them. - -## CI and acceptance criteria - -### Recommended CI stages - -1. **Fast JVM stage:** compile all variants and run deterministic JVM tests on every supported Minecraft version. -2. **Core client stage:** run lifecycle, input, bindings, movement, rumble, and resource-pack tests on the primary Fabric version. -3. **Version smoke stage:** run lifecycle, one input/action flow, one rumble flow, and one resource reload on each additional supported Fabric version. -4. **Compatibility stage:** scheduled or change-triggered profiles for YACL and optional mods. -5. **Hardware stage:** opt-in/manual jobs for Steam Deck, HD haptics, native keyboard, Bluetooth/power, and representative physical controllers. - -### Reliability rules - -- Every wait must have a bounded timeout and report the semantic state it was awaiting. -- Never use a previous output as proof of a new event; wait on an output sequence number. -- Use approximate comparisons for analogue/camera values and exact comparisons for digital states and encoded bytes. -- Fix world seed/settings, window resolution, GUI scale, language, and pack selection where relevant. -- Avoid real-time sleeps; advance game ticks through the test context. -- Avoid assertions based only on log messages. -- Keep screenshots out of tests whose behavior can be asserted through state. -- A test that changes persistent files must use an isolated game/config directory or restore from an exact snapshot. -- Test helpers should fail at the mutation/assertion site with controller ID, tick, input/output history, and current screen in the error message. - -### Definition of useful coverage - -A feature is meaningfully covered when tests verify its public or player-visible contract across the relevant boundary. Examples: - -- A mapping codec test alone does not cover controller mappings; at least one client test must prove mapped virtual input reaches a binding. -- A guide rule codec test alone does not cover data-driven guides; a reload test must prove pack priority and a guide instance must resolve the expected actions. -- A rumble mixin invocation alone does not cover rumble; the virtual device must observe the expected output and eventual stop. -- A screen screenshot alone does not cover controller navigation; injected controller input must change focus or activate the intended action. - -## Recommended implementation order - -1. Controller entity lookup, checked/tick-aware input, hats/touchpads, and detach waiting. -2. Output histories and DualSense decoding. -3. Lifecycle, input translation, binding, deadzone/mapping, and input-mode suites. -4. JVM mapping, rumble, DualSense, codec, packet, and migration suites. -5. Movement, look, gyro, core gameplay actions, and expanded rumble suites. -6. Config and resource-pack fixtures, followed by every data-driven manager's priority/reload tests. -7. Server policies, commands, reach-around, and gameplay patches. -8. Screen navigation, virtual mouse, radial menus, guides, glyphs, and keyboard. -9. Persistence, optional-mod profiles, loader parity, and hardware-dependent suites. - -This order builds reusable observability first, then covers the contracts with the largest blast radius, and leaves visually brittle or environment-dependent tests until the deterministic core is protected. diff --git a/assets/icon-256-bg.png b/assets/icon-256-bg.png new file mode 100644 index 000000000..ea0119671 Binary files /dev/null and b/assets/icon-256-bg.png differ diff --git a/assets/icon-256.png b/assets/icon-256.png new file mode 100644 index 000000000..dcb709481 Binary files /dev/null and b/assets/icon-256.png differ diff --git a/docs/.assets/controlify/images/button-glyph-atlas.png b/docs/.assets/controlify/images/button-glyph-atlas.png deleted file mode 100644 index a45a771d3..000000000 Binary files a/docs/.assets/controlify/images/button-glyph-atlas.png and /dev/null differ diff --git a/docs/.assets/controlify/images/container_guide.png b/docs/.assets/controlify/images/container_guide.png deleted file mode 100644 index 60cbd3aba..000000000 Binary files a/docs/.assets/controlify/images/container_guide.png and /dev/null differ diff --git a/docs/.assets/controlify/images/in_game_guide.png b/docs/.assets/controlify/images/in_game_guide.png deleted file mode 100644 index be2c85ab5..000000000 Binary files a/docs/.assets/controlify/images/in_game_guide.png and /dev/null differ diff --git a/docs/.assets/controlify/images/options-button-with-glyph.png b/docs/.assets/controlify/images/options-button-with-glyph.png deleted file mode 100644 index 1bcb53623..000000000 Binary files a/docs/.assets/controlify/images/options-button-with-glyph.png and /dev/null differ diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..1e729dc43 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,36 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo +.vitepress/cache +.wrangler + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 000000000..73b74fd59 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,139 @@ +import { defineConfig } from 'vitepress' +import { readdirSync } from "node:fs"; +import { basename, extname } from "node:path"; +import type { LanguageInput } from '@shikijs/types' +import mcfunctionGrammar from './grammars/mcfunction.tmLanguage.json' with { type: 'json' } + +const changelogEntries = readdirSync("./content/changelog") + .filter(fileName => extname(fileName) === ".md") + .filter(fileName => fileName !== "index.md") + .sort((first, second) => + second.localeCompare(first, undefined, { numeric: true }) + ) + .map(fileName => { + const version = basename(fileName, ".md"); + + return { + text: version, + link: `/changelog/${version}`, + }; + }); + +export default defineConfig({ + srcDir: 'content', + vite: { publicDir: '../public' }, + title: 'Controlify', + description: 'Complete controller support for Minecraft: Java Edition.', + cleanUrls: true, + lastUpdated: true, + markdown: { + languages: [ + 'java', + 'json', + 'json5', + { + ...mcfunctionGrammar, + name: 'mcfunction', + aliases: ['mcfunc'] + } as unknown as LanguageInput + ] + }, + head: [ + ['link', { rel: 'icon', type: 'image/png', sizes: '256x256', href: '/icon-256-bg.png' }], + ['meta', { name: 'theme-color', content: '#fafd9e' }], + ['meta', { property: 'og:image', content: '/icon-256-bg.png' }] + ], + themeConfig: { + logo: '/icon-256-bg.png', + siteTitle: 'Controlify', + search: { provider: 'local' }, + nav: [ + { text: 'Players', link: '/users/controller-support' }, + { text: 'Resource Packs', link: '/resource-packs/custom-controller-identification' }, + { text: 'Developers', link: '/developers/getting-started' }, + { text: 'Reference', link: '/reference/builtin-bindings' }, + { + text: 'Download', + items: [ + { text: 'Modrinth', link: 'https://modrinth.com/mod/controlify' }, + { text: 'CurseForge', link: 'https://www.curseforge.com/minecraft/mc-mods/controlify' } + ] + } + ], + sidebar: [ + { + text: 'Players', + collapsed: false, + items: [ + { + text: 'Controller Support', + link: '/users/controller-support', + items: [ + { text: "Playstation Controllers", link: '/users/controller-support/playstation-controllers' }, + { text: 'Valve Hardware', link: '/users/controller-support/valve-hardware' } + ] + }, + { text: 'Mod Comparison', link: '/users/mod-comparison' } + ] + }, + { + text: 'Resource Packs', + collapsed: false, + items: [ + { text: 'Controller Identification', link: '/resource-packs/custom-controller-identification' }, + { text: 'Default Binds', link: '/resource-packs/default-binds' }, + { text: 'Input Glyphs', link: '/resource-packs/input-glyphs' }, + { text: 'Button Guides', link: '/resource-packs/guides' }, + { text: 'Keyboard Layouts', link: '/resource-packs/keyboard-layouts' }, + { text: 'Radial Menu Icons', link: '/resource-packs/radial-icons' }, + { text: 'Adaptive Trigger Effects', link: '/resource-packs/adaptive-trigger-effects' } + ] + }, + { + text: 'Developers', + collapsed: false, + items: [ + { text: 'Getting Started', link: '/developers/getting-started' }, + { text: 'Controlify Entrypoint', link: '/developers/controlify-entrypoint' }, + { text: 'Bindings API', link: '/developers/bindings-api' }, + { text: 'Screen Operation API', link: '/developers/screen-operation-api' }, + { text: 'Guides API', link: '/developers/guide-api' }, + { text: 'Adaptive Trigger API', link: '/developers/adaptive-trigger-api' } + ] + }, + { + text: 'Changelog', + collapsed: true, + items: changelogEntries + }, + { + text: 'Reference', + collapsed: false, + items: [ + { text: 'Built-in Bindings', link: '/reference/builtin-bindings' }, + { text: 'Built-in Inputs', link: '/reference/builtin-inputs' }, + { text: 'Controller Namespaces', link: '/reference/builtin-controller-namespaces' } + ] + }, + { + text: 'Architecture', + items: [{ text: 'Controllers', link: '/architecture/controllers' }] + } + ], + outline: { level: [2, 3], label: 'On this page' }, + editLink: { + pattern: 'https://github.com/isXander/Controlify/edit/multiversion/dev/docs/content/:path', + text: 'Edit this page on GitHub' + }, + socialLinks: [ + { icon: 'github', link: 'https://github.com/isXander/Controlify' }, + { icon: 'discord', link: 'https://short.isxander.dev/discord' } + ], + footer: { + message: 'Controlify is an open-source Minecraft mod.', + copyright: 'Made with care by isXander and contributors.' + }, + lastUpdated: { text: 'Updated' }, + docFooter: { prev: 'Previous', next: 'Next' } + } +}) diff --git a/docs/.vitepress/data/controllerCompatibility.ts b/docs/.vitepress/data/controllerCompatibility.ts new file mode 100644 index 000000000..c0a564cc7 --- /dev/null +++ b/docs/.vitepress/data/controllerCompatibility.ts @@ -0,0 +1,100 @@ +export enum Controller { + XInput = 'XInput-compatible controller', + XboxEliteSeries2 = 'Xbox Elite Series 2', + SonyDualSense = 'Sony DualSense', + SteamDeck = 'Steam Deck', + GoogleStadia = 'Google Stadia', + SteamController = 'Steam Controller' +} + +export enum Connection { + NotApplicable = 'Not applicable', + USB = 'USB', + Bluetooth = 'Bluetooth', + BuiltIn = 'Built in' +} + +export enum OperatingSystem { + Windows = 'Windows', + Linux = 'Linux', + MacOS = 'macOS', + SteamOS = 'SteamOS' +} + +export enum SupportStatus { + Works = 'works', + SetupRequired = 'setup-required', + Partial = 'partial', + Unsupported = 'unsupported', + NotApplicable = 'not-applicable' +} + +export const compatibilityFeatures = [ + 'Standard input', + 'Rumble', + 'Extra inputs', + 'Gyro', + 'Touchpad', + 'HD haptics', + 'Adaptive triggers' +] as const + +export type FeatureSupport = readonly [ + standardInput: SupportStatus, + rumble: SupportStatus, + extraInputs: SupportStatus, + gyro: SupportStatus, + touchpad: SupportStatus, + hdHaptics: SupportStatus, + adaptiveTriggers: SupportStatus +] + +export interface CompatibilityEntry { + readonly controller: Controller + readonly connection: Connection + readonly operatingSystem: OperatingSystem + readonly support: FeatureSupport +} + +export const controllerGuides: Partial> = { + [Controller.SonyDualSense]: '/users/controller-support/playstation-controllers', + [Controller.SteamDeck]: '/users/controller-support/valve-hardware#steam-deck', + [Controller.GoogleStadia]: '/users/controller-support/stadia-controller', + [Controller.SteamController]: '/users/controller-support/valve-hardware#steam-controller-2026' +} + +const { XInput, XboxEliteSeries2: Elite, SonyDualSense: DualSense, SteamDeck: Deck, GoogleStadia: Stadia } = Controller +const { NotApplicable: NoConnection, USB, Bluetooth, BuiltIn } = Connection +const { Windows, Linux, MacOS, SteamOS } = OperatingSystem +const { Works: Y, SetupRequired: S, Unsupported: N, NotApplicable: NA } = SupportStatus + +const row = ( + controller: Controller, + connection: Connection, + operatingSystem: OperatingSystem, + support: FeatureSupport +): CompatibilityEntry => ({ controller, connection, operatingSystem, support }) + +export const compatibilityMatrix = [ + row(XInput, NoConnection, Windows, [Y, Y, NA, NA, NA, NA, NA]), + row(Elite, USB, Windows, [Y, Y, N, NA, NA, NA, NA]), + row(Elite, USB, Linux, [Y, Y, Y, NA, NA, NA, NA]), + row(Elite, USB, MacOS, [Y, Y, Y, NA, NA, NA, NA]), + + row(DualSense, USB, Windows, [Y, Y, Y, Y, Y, Y, Y]), + row(DualSense, Bluetooth, Windows, [Y, Y, Y, Y, Y, N, Y]), + row(DualSense, USB, Linux, [Y, Y, Y, Y, S, Y, Y]), + row(DualSense, Bluetooth, Linux, [Y, Y, Y, Y, S, N, Y]), + row(DualSense, USB, MacOS, [Y, Y, Y, Y, Y, N, Y]), + row(DualSense, Bluetooth, MacOS, [Y, Y, Y, Y, Y, N, Y]), + + row(Deck, BuiltIn, SteamOS, [Y, Y, N, N, N, NA, NA]), + row(Deck, BuiltIn, Windows, [Y, Y, Y, Y, Y, NA, NA]), + + row(Stadia, USB, Windows, [Y, Y, Y, NA, NA, NA, NA]), + row(Stadia, USB, Linux, [Y, Y, Y, NA, NA, NA, NA]), + row(Stadia, USB, MacOS, [Y, Y, Y, NA, NA, NA, NA]), + row(Stadia, Bluetooth, Windows, [S, N, S, NA, NA, NA, NA]), + row(Stadia, Bluetooth, Linux, [S, S, S, NA, NA, NA, NA]), + row(Stadia, Bluetooth, MacOS, [S, S, S, NA, NA, NA, NA]) +] as const satisfies readonly CompatibilityEntry[] diff --git a/docs/.vitepress/grammars/LICENSE.syntax-mcfunction b/docs/.vitepress/grammars/LICENSE.syntax-mcfunction new file mode 100644 index 000000000..5319e207f --- /dev/null +++ b/docs/.vitepress/grammars/LICENSE.syntax-mcfunction @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2023 MinecraftCommands + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/.vitepress/grammars/README.md b/docs/.vitepress/grammars/README.md new file mode 100644 index 000000000..1555270d7 --- /dev/null +++ b/docs/.vitepress/grammars/README.md @@ -0,0 +1,11 @@ +# Vendored syntax grammars + +## mcfunction + +- Source: https://github.com/MinecraftCommands/syntax-mcfunction +- Version: 1.0.1 +- Commit: `51eb8bf4ca04355bb89f5538b7331cb0c0f3df2b` +- License: MIT; see `LICENSE.syntax-mcfunction` + +The grammar's registered name is overridden to `mcfunction` in the VitePress +configuration so it matches the language identifier used by Markdown fences. diff --git a/docs/.vitepress/grammars/mcfunction.tmLanguage.json b/docs/.vitepress/grammars/mcfunction.tmLanguage.json new file mode 100644 index 000000000..4d0c0f8a2 --- /dev/null +++ b/docs/.vitepress/grammars/mcfunction.tmLanguage.json @@ -0,0 +1,740 @@ +{ + "name": "Syntax Mcfunction", + "scopeName": "source.mcfunction", + "uuid": "8918dadd-8ebe-42d9-b1e9-0489ab228d9d", + "fileTypes": [ + "mcfunction", + "bolt" + ], + "patterns": [ + { + "include": "#root" + } + ], + "repository": { + "root": { + "patterns": [ + { + "include": "#literals" + }, + { + "include": "#comments" + }, + { + "include": "#say" + }, + { + "include": "#names" + }, + { + "include": "#comments_inline" + }, + { + "include": "#subcommands" + }, + { + "include": "#property" + }, + { + "include": "#operators" + }, + { + "include": "#selectors" + } + ] + }, + "comments": { + "patterns": [ + { + "applyEndPatternLast": 1, + "begin": "^\\s*(#[>!#])(.+)$", + "beginCaptures": { + "1": { + "name": "comment.block.mcfunction" + }, + "2": { + "name": "markup.bold.mcfunction" + } + }, + "captures": { + "0": { + "name": "comment.block.mcfunction" + } + }, + "end": "^(?!#)", + "name": "meta.comments", + "patterns": [ + { + "include": "#comments_block" + } + ] + }, + { + "captures": { + "0": { + "name": "comment.line.mcfunction" + } + }, + "match": "^\\s*#.*$", + "name": "meta.comments" + } + ] + }, + "comments_inline": { + "patterns": [ + { + "captures": { + "0": { + "name": "comment.line.mcfunction" + } + }, + "match": "#.*$", + "name": "meta.comments" + } + ] + }, + "comments_block": { + "patterns": [ + { + "applyEndPatternLast": 1, + "begin": "^\\s*#[>!]", + "captures": { + "0": { + "name": "comment.block.mcfunction" + } + }, + "end": "$", + "name": "meta.comments_block", + "patterns": [ + { + "include": "#comments_block_emphasized" + } + ] + }, + { + "applyEndPatternLast": 1, + "begin": "^\\s*#", + "captures": { + "0": { + "name": "comment.block.mcfunction" + } + }, + "end": "$", + "name": "meta.comments_block", + "patterns": [ + { + "include": "#comments_block_normal" + } + ] + } + ] + }, + "comments_block_emphasized": { + "patterns": [ + { + "include": "#comments_block_special" + }, + { + "captures": { + "0": { + "name": "markup.bold.mcfunction" + } + }, + "match": "\\S+", + "name": "meta.comments_block_emphasized" + } + ] + }, + "comments_block_normal": { + "patterns": [ + { + "include": "#comments_block_special" + }, + { + "captures": { + "0": { + "name": "comment.block.mcfunction" + } + }, + "match": "\\S+", + "name": "meta.comments_block_normal" + }, + { + "include": "#whitespace" + } + ] + }, + "comments_block_special": { + "patterns": [ + { + "captures": { + "0": { + "name": "markup.heading.mcfunction" + } + }, + "match": "@\\S+", + "name": "meta.comments_block_special" + }, + { + "include": "#resource-name" + }, + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "[#%$][A-Za-z0-9_.#%$]+", + "name": "meta.comments_block_special" + } + ] + }, + "literals": { + "patterns": [ + { + "captures": { + "0": { + "name": "constant.numeric.boolean.mcfunction" + } + }, + "match": "\\b(true|false|True|False)\\b", + "name": "meta.literals" + }, + { + "captures": { + "0": { + "name": "variable.uuid.mcfunction" + } + }, + "match": "\\b[0-9a-fA-F]+(?:-[0-9a-fA-F]+){4}\\b", + "name": "meta.names" + }, + { + "captures": { + "0": { + "name": "constant.numeric.float.mcfunction" + } + }, + "match": "[+-]?\\d*\\.?\\d+([eE]?[+-]?\\d+)?[df]?\\b", + "name": "meta.literals" + }, + { + "captures": { + "0": { + "name": "constant.numeric.integer.mcfunction" + } + }, + "match": "[+-]?\\d+(b|B|L|l|s|S)?\\b", + "name": "meta.literals" + }, + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "\\.\\.", + "name": "meta.ellipse.literals" + }, + { + "applyEndPatternLast": 1, + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.mcfunction" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.mcfunction" + } + }, + "name": "string.quoted.double.mcfunction", + "patterns": [ + { + "include": "#literals_string-double" + } + ] + }, + { + "applyEndPatternLast": 1, + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.mcfunction" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.begin.mcfunction" + } + }, + "name": "string.quoted.single.mcfunction", + "patterns": [ + { + "include": "#literals_string-single" + } + ] + } + ] + }, + "subcommands": { + "patterns": [ + { + "captures": { + "0": { + "name": "entity.name.class.mcfunction" + } + }, + "match": "[a-z_]+", + "name": "meta.literals" + } + ] + }, + "literals_string-double": { + "patterns": [ + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\.", + "name": "meta.literals_string-double" + }, + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\", + "name": "meta.literals_string-double" + }, + { + "include": "#macro-name" + }, + { + "captures": { + "0": { + "name": "string.quoted.double.mcfunction" + } + }, + "match": "[^\\\\\"]", + "name": "meta.literals_string-double" + } + ] + }, + "literals_string-single": { + "patterns": [ + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\.", + "name": "meta.literals_string-single" + }, + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\", + "name": "meta.literals_string-double" + }, + { + "include": "#macro-name" + }, + { + "captures": { + "0": { + "name": "string.quoted.single.mcfunction" + } + }, + "match": "[^\\\\']", + "name": "meta.literals_string-single" + } + ] + }, + "say": { + "patterns": [ + { + "begin": "^(\\s*)(say)", + "beginCaptures": { + "1": { + "name": "whitespace.mcfunction" + }, + "2": { + "name": "keyword.control.flow.mcfunction" + } + }, + "end": "\\n", + "name": "meta.say.mcfunction", + "patterns": [ + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\\\s*\\n", + "meta": "meta.say.backslash.mcfunction" + }, + { + "include": "#literals_string-double" + }, + { + "include": "#literals_string-single" + } + ] + }, + { + "begin": "(run)(\\s+)(say)", + "beginCaptures": { + "1": { + "name": "entity.name.mcfunction" + }, + "2": { + "name": "whitespace.mcfunction" + }, + "3": { + "name": "keyword.control.flow.mcfunction" + } + }, + "end": "\\n", + "name": "meta.say.mcfunction", + "patterns": [ + { + "captures": { + "0": { + "name": "constant.character.escape.mcfunction" + } + }, + "match": "\\\\\\s*\\n", + "meta": "meta.say.backslash.mcfunction" + }, + { + "include": "#literals_string-double" + }, + { + "include": "#literals_string-single" + } + ] + } + ] + }, + "names": { + "patterns": [ + { + "captures": { + "1": { + "name": "whitespace.mcfunction" + }, + "2": { + "name": "keyword.control.flow.mcfunction" + } + }, + "match": "^(\\s*)([a-z_]+)(?=\\s)", + "name": "meta.names" + }, + { + "captures": { + "1": { + "name": "whitespace.mcfunction" + }, + "2": { + "name": "markup.italic.mcfunction" + }, + "3": { + "name": "whitespace.mcfunction" + }, + "4": { + "name": "keyword.control.flow.mcfunction" + } + }, + "match": "^(\\s*)(\\$)( ?)([a-z_]*)", + "name": "meta.names" + }, + { + "captures": { + "1": { + "name": "entity.name.mcfunction" + }, + "2": { + "name": "whitespace.mcfunction" + }, + "3": { + "name": "keyword.control.flow.mcfunction" + } + }, + "match": "(run)(\\s+)([a-z_]+)", + "name": "meta.names" + }, + { + "include": "#resource-name" + }, + { + "captures": { + "0": { + "name": "entity.name.mcfunction" + } + }, + "match": "[A-Za-z]+(?=\\W)", + "name": "meta.names" + }, + { + "captures": { + "0": { + "name": "string.unquoted.mcfunction" + } + }, + "match": "[A-Za-z_][A-Za-z0-9_.#%$]*", + "name": "meta.names" + }, + { + "include": "#macro-name" + }, + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "([#%$]|((?<=\\s)\\.))[A-Za-z0-9_.#%$\\-]+", + "name": "meta.names" + } + ] + }, + "macro-name": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.template-expression.begin.mcfunction" + }, + "2": { + "name": "variable.other.mcfunction" + }, + "3": { + "name": "punctuation.definition.template-expression.end.mcfunction" + } + }, + "match": "(\\$\\()([A-Za-z0-9_]*)(\\))", + "name": "meta.macro-name" + } + ] + }, + "operators": { + "patterns": [ + { + "captures": { + "0": { + "name": "constant.numeric.mcfunction" + } + }, + "match": "[~^]", + "name": "meta.operators" + }, + { + "captures": { + "0": { + "name": "keyword.operator.mcfunction" + } + }, + "match": "[\\-%?!+*<>\\\\/|&=.:,;]", + "name": "meta.operators" + } + ] + }, + "property": { + "patterns": [ + { + "applyEndPatternLast": 1, + "begin": "\\{", + "captures": { + "0": { + "name": "punctuation.mcfunction" + } + }, + "end": "\\}", + "name": "meta.property.curly", + "patterns": [ + { + "include": "#resource-name" + }, + { + "include": "#literals" + }, + { + "include": "#property_key" + }, + { + "include": "#operators" + }, + { + "include": "#property_value" + }, + { + "include": "$self" + } + ] + }, + { + "applyEndPatternLast": 1, + "begin": "\\[", + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "end": "\\]", + "name": "meta.property.square", + "patterns": [ + { + "include": "#resource-name" + }, + { + "include": "#literals" + }, + { + "include": "#property_key" + }, + { + "include": "#operators" + }, + { + "include": "#property_value" + }, + { + "include": "$self" + } + ] + }, + { + "applyEndPatternLast": 1, + "begin": "\\(", + "captures": { + "0": { + "name": "punctuation.mcfunction" + } + }, + "end": "\\)", + "name": "meta.property.paren", + "patterns": [ + { + "include": "#resource-name" + }, + { + "include": "#literals" + }, + { + "include": "#property_key" + }, + { + "include": "#operators" + }, + { + "include": "#property_value" + }, + { + "include": "$self" + } + ] + } + ] + }, + "property_key": { + "patterns": [ + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "#?[a-z_][a-z_\\.\\-]*\\:[a-z0-9_\\.\\-/]+(?=\\s*\\=:)", + "name": "meta.property_key" + }, + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "#?[a-z_][a-z0-9_\\.\\-/]+", + "name": "meta.property_key" + }, + { + "captures": { + "0": { + "name": "variable.other.mcfunction" + } + }, + "match": "[A-Za-z_]+[A-Za-z_\\-\\+]*", + "name": "meta.property_key" + } + ] + }, + "property_value": { + "patterns": [ + { + "captures": { + "0": { + "name": "string.unquoted.mcfunction" + } + }, + "match": "#?[a-z_][a-z_\\.\\-]*\\:[a-z0-9_\\.\\-/]+", + "name": "meta.property_value" + }, + { + "captures": { + "0": { + "name": "string.unquoted.mcfunction" + } + }, + "match": "#?[a-z_][a-z0-9_\\.\\-/]+", + "name": "meta.property_value" + } + ] + }, + "resource-name": { + "patterns": [ + { + "captures": { + "0": { + "name": "entity.name.function.mcfunction" + } + }, + "match": "#?[a-z_][a-z0-9_.-]*:[a-z0-9_./-]+", + "name": "meta.resource-name" + }, + { + "captures": { + "0": { + "name": "entity.name.function.mcfunction" + } + }, + "match": "#?[a-z0-9_\\.\\-]+\\/[a-z0-9_\\.\\-\\/]+", + "name": "meta.resource-name" + } + ] + }, + "selectors": { + "patterns": [ + { + "captures": { + "0": { + "name": "support.class.mcfunction" + } + }, + "match": "@[a-z]+", + "name": "meta.selectors" + } + ] + } + } +} diff --git a/docs/.vitepress/theme/components/ControlifyHome.vue b/docs/.vitepress/theme/components/ControlifyHome.vue new file mode 100644 index 000000000..c62ec02da --- /dev/null +++ b/docs/.vitepress/theme/components/ControlifyHome.vue @@ -0,0 +1,315 @@ + + + diff --git a/docs/.vitepress/theme/components/ControllerCompatibility.vue b/docs/.vitepress/theme/components/ControllerCompatibility.vue new file mode 100644 index 000000000..186e01e02 --- /dev/null +++ b/docs/.vitepress/theme/components/ControllerCompatibility.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/docs/.vitepress/theme/docs.css b/docs/.vitepress/theme/docs.css new file mode 100644 index 000000000..bd184dbcc --- /dev/null +++ b/docs/.vitepress/theme/docs.css @@ -0,0 +1,20 @@ +.wiki-image-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.wiki-image-grid p { + margin: 0; +} + +.wiki-image-grid img, +.vp-doc > p > img { + border-radius: 10px; +} + +@media (max-width: 520px) { + .wiki-image-grid { + grid-template-columns: 1fr; + } +} diff --git a/docs/.vitepress/theme/home.css b/docs/.vitepress/theme/home.css new file mode 100644 index 000000000..a164d4f43 --- /dev/null +++ b/docs/.vitepress/theme/home.css @@ -0,0 +1,1247 @@ +.controlify-home-page { + --home-ink: #10110e; + --home-paper: #f1f2e8; + --home-acid: #eef256; + --home-acid-bright: #f8fb77; + --home-blue: #6d92c8; + --home-blue-dark: #17263e; + --home-line: rgba(16, 17, 14, 0.16); + background: var(--home-paper); +} + +.controlify-home-page .VPNavBar { + border-bottom-color: rgba(238, 242, 86, 0.12); + color: #fff; + background: rgba(16, 17, 14, 0.86); + backdrop-filter: blur(16px); +} + +.Layout.controlify-home-page .VPNavBar.home:not(.top), +.Layout.controlify-home-page .VPNavBar.screen-open { + border-bottom-color: rgba(238, 242, 86, 0.12); + background: rgba(16, 17, 14, 0.86); +} + +.controlify-home-page .VPNavBar .title, +.controlify-home-page .VPNavBar .VPNavBarMenuLink, +.controlify-home-page .VPNavBar .VPNavBarMenuGroup .text, +.controlify-home-page .VPNavBar .VPSocialLink, +.controlify-home-page .VPNavBar .VPNavBarSearchButton, +.controlify-home-page .VPNavBar .VPSwitchAppearance, +.controlify-home-page .VPNavBar .VPNavBarHamburger { + color: rgba(255, 255, 255, 0.88); +} + +.controlify-home-page .VPNavBar .VPNavBarMenuLink:hover, +.controlify-home-page .VPNavBar .VPNavBarMenuLink.active, +.controlify-home-page .VPNavBar .VPNavBarMenuGroup:hover .text { + color: var(--home-acid); +} + +.controlify-home-page .VPNavBar .VPSwitchAppearance { + border-color: rgba(255, 255, 255, 0.28); + background: rgba(255, 255, 255, 0.08); +} + +.controlify-home-page .VPNavBar .VPSwitchAppearance:hover { + border-color: var(--home-acid); +} + +.controlify-home-page .VPNavBar .VPSwitchAppearance .check { + background: var(--home-acid); + box-shadow: none; +} + +.controlify-home-page .VPNavBar .VPSwitchAppearance .icon [class^='vpi-'] { + color: var(--home-ink); +} + +@media (min-width: 768px) { + .controlify-home-page .VPNavBar .VPNavBarSearchButton { + border: 1px solid rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.72); + background: rgba(255, 255, 255, 0.06); + } + + .controlify-home-page .VPNavBar .VPNavBarSearchButton:hover { + border-color: rgba(238, 242, 86, 0.48); + color: #fff; + background: rgba(255, 255, 255, 0.1); + } + + .controlify-home-page .VPNavBar .VPNavBarSearchButton .keys { + border-color: rgba(255, 255, 255, 0.18); + background: rgba(0, 0, 0, 0.14); + } +} + +.controlify-home-page .VPNavBarTitle .logo { + image-rendering: pixelated; +} + +.controlify-home-page .VPContent { + padding-top: 0; +} + +.controlify-home-page .VPHome { + margin-bottom: 0; +} + +.controlify-home-page .VPFooter { + margin: 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); + background: var(--home-ink); +} + +.controlify-home-page .VPFooter .message, +.controlify-home-page .VPFooter .copyright { + color: rgba(255, 255, 255, 0.55); +} + +.controlify-home { + overflow: hidden; + color: var(--home-ink); + background: var(--home-paper); +} + +.controlify-home *, +.controlify-home *::before, +.controlify-home *::after { + box-sizing: border-box; +} + +.controlify-home a { + color: inherit; + text-decoration: none; +} + +.controlify-home a:focus-visible { + outline: 3px solid var(--home-acid); + outline-offset: 4px; +} + +.home-shell { + width: min(100% - 48px, 1320px); + margin: 0 auto; +} + +.home-platform { + display: inline-flex; + align-items: center; + min-height: 28px; + margin: 0; + padding: 0 10px; + color: var(--home-ink); + background: var(--home-acid); + font-family: var(--vp-font-family-mono); + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.home-hero { + position: relative; + isolation: isolate; + min-height: min(920px, 100svh); + padding: 154px 0 96px; + color: #fff; + background: + radial-gradient(circle at 78% 32%, rgba(109, 146, 200, 0.18), transparent 28%), + var(--home-ink); +} + +.home-hero__grid { + position: absolute; + z-index: -1; + inset: 0; + opacity: 0.34; + background-image: + linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px); + background-size: 64px 64px; + mask-image: linear-gradient(to bottom, #000 10%, transparent 90%); +} + +.home-hero__layout { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(400px, 0.92fr); + align-items: center; + gap: clamp(42px, 7vw, 112px); +} + +.home-hero__copy { + position: relative; + z-index: 2; +} + +.home-hero h1, +.controller-section h2, +.section-heading h2, +.download-section h2 { + margin: 0; + border: 0; + font-family: 'Space Grotesk', var(--vp-font-family-base); + font-weight: 700; + letter-spacing: -0.075em; + line-height: 0.82; + overflow-wrap: normal; + text-transform: uppercase; + word-break: normal; + hyphens: none; +} + +.home-hero h1 { + margin-top: 28px; + font-size: clamp(4.1rem, 6.8vw, 7.1rem); +} + +.home-hero h1 span, +.home-hero h1 strong { + display: block; +} + +.home-hero h1 span { + color: transparent; + -webkit-text-stroke: 2px rgba(255, 255, 255, 0.9); +} + +.home-hero h1 strong { + margin-top: 0.15em; + color: var(--home-acid); + font-weight: inherit; +} + +.home-hero__summary { + max-width: 650px; + margin: 36px 0 0; + color: rgba(255, 255, 255, 0.7); + font-size: clamp(1rem, 1.35vw, 1.18rem); + line-height: 1.72; +} + +.home-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 36px; +} + +.home-button { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 28px; + min-width: 210px; + min-height: 54px; + padding: 0 22px; + border: 1px solid transparent; + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.03em; + transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease, transform 180ms ease; +} + +.home-button:hover { + transform: translateY(-3px); +} + +.home-button--acid { + color: var(--home-ink) !important; + background: var(--home-acid); +} + +.home-button--acid:hover { + background: var(--home-acid-bright); +} + +.home-button--outline { + border-color: rgba(255, 255, 255, 0.3); + color: #fff !important; + background: rgba(255, 255, 255, 0.03); +} + +.home-button--outline:hover { + border-color: rgba(255, 255, 255, 0.65); + background: rgba(255, 255, 255, 0.08); +} + +.home-hero__facts { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 48px 0 0; + color: rgba(255, 255, 255, 0.42); + font-family: var(--vp-font-family-mono); + font-size: 0.62rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.home-hero__facts i { + color: var(--home-acid); + font-style: normal; +} + +.controller-display { + position: relative; + isolation: isolate; + display: grid; + place-items: center; + aspect-ratio: 1; + min-width: 0; +} + +.controller-display::before { + content: ''; + position: absolute; + z-index: -1; + top: 50%; + left: 50%; + width: 78%; + height: 58%; + background: var(--home-acid); + box-shadow: 20px 20px 0 rgba(109, 146, 200, 0.18); + transform: translate(-52%, -48%) rotate(-5deg); +} + +.controller-display__controller { + width: 94%; + image-rendering: pixelated; + filter: drop-shadow(0 28px 18px rgba(0, 0, 0, 0.42)); + animation: home-controller-float 4s ease-in-out infinite; +} + +.controller-section { + padding: clamp(96px, 11vw, 160px) 0 clamp(100px, 12vw, 176px); + background: var(--home-acid); +} + +.controller-section__header { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr); + align-items: end; + gap: 32px 72px; +} + +.controller-section h2, +.section-heading h2, +.download-section h2 { + font-size: clamp(3.8rem, 8vw, 8.7rem); +} + +.section-heading--split h2 { + font-size: clamp(3.8rem, 7vw, 8.3rem); +} + +.controller-section__header > div > p, +.section-heading > p, +.section-heading > div > p { + margin: 0; + font-size: 1.03rem; + line-height: 1.72; +} + +.home-text-link { + display: inline-flex; + align-items: center; + gap: 18px; + margin-top: 24px; + padding-bottom: 5px; + border-bottom: 1px solid currentColor; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.controller-lanes { + display: grid; + gap: 18px; + margin-top: clamp(64px, 8vw, 108px); +} + +.controller-lane { + overflow: hidden; + width: 100%; + mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); +} + +.controller-lane__track { + display: flex; + width: max-content; + animation: home-controller-scroll 34s linear infinite; + will-change: transform; +} + +.controller-lane--reverse .controller-lane__track { + animation-direction: reverse; + animation-duration: 40s; +} + +.controller-lane__group { + display: flex; + gap: 18px; + padding-right: 18px; +} + +.controller-chip { + display: grid; + grid-template-columns: 94px auto; + align-items: center; + flex: 0 0 auto; + min-width: 260px; + height: 116px; + padding: 10px 26px 10px 12px; + border: 2px solid var(--home-ink); + background: rgba(241, 242, 232, 0.54); + box-shadow: 7px 7px 0 var(--home-ink); +} + +.controller-chip img { + width: 82px; + height: 82px; + object-fit: contain; + image-rendering: pixelated; +} + +.controller-chip span { + font-family: var(--vp-font-family-mono); + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.integration-section { + padding: clamp(110px, 13vw, 190px) 0; + background: var(--home-paper); +} + +.section-heading--split, +.section-heading--multiplayer { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr); + align-items: end; + gap: 40px 80px; +} + +.section-heading--split > p { + padding-bottom: 7px; + color: rgba(16, 17, 14, 0.68); +} + +.integration-gallery { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + grid-template-rows: repeat(2, minmax(240px, 1fr)); + gap: 16px; + min-height: 720px; + margin-top: 76px; +} + +.game-shot { + position: relative; + overflow: hidden; + margin: 0; + border: 2px solid var(--home-ink); + background: var(--home-ink); +} + +.game-shot > img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 500ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.game-shot:hover > img { + transform: scale(1.025); +} + +.game-shot figcaption { + position: absolute; + right: 18px; + bottom: 18px; + left: 18px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + min-height: 48px; + padding: 10px 14px; + color: #fff; + background: rgba(16, 17, 14, 0.88); + backdrop-filter: blur(10px); +} + +.game-shot figcaption strong { + font-family: 'Space Grotesk', var(--vp-font-family-base); + font-size: 1rem; + text-transform: uppercase; +} + +.game-shot figcaption a { + color: inherit; + text-decoration-line: underline; + text-decoration-color: var(--home-acid); + text-decoration-thickness: 2px; + text-underline-offset: 4px; + transition: color 160ms ease, text-decoration-color 160ms ease; +} + +.game-shot figcaption a:hover { + color: var(--home-acid); + text-decoration-color: currentColor; +} + +.game-shot figcaption span { + color: rgba(255, 255, 255, 0.6); + font-size: 0.72rem; +} + +.game-shot--sodium { + grid-column: span 7; + grid-row: 1 / -1; +} + +.game-shot--minimega-glide, +.game-shot--journeymap-fullscreen { + grid-column: span 5; +} + +.integration-docs { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + margin-top: 18px; +} + +.integration-doc { + display: flex; + flex-direction: column; + min-height: 330px; + padding: 30px; + border: 2px solid var(--home-ink); + background: #e4e5da; + transition: color 180ms ease, background-color 180ms ease, transform 180ms ease; +} + +.integration-doc:hover { + color: var(--home-ink); + background: var(--home-acid); + transform: translateY(-4px); +} + +.integration-doc > span { + font-family: var(--vp-font-family-mono); + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.integration-doc h3 { + max-width: 590px; + margin: 38px 0 16px; + color: inherit; + font-family: 'Space Grotesk', var(--vp-font-family-base); + font-size: clamp(1.8rem, 3.2vw, 3.2rem); + letter-spacing: -0.055em; + line-height: 0.95; + text-transform: uppercase; +} + +.integration-doc p { + max-width: 590px; + margin: 0; + color: rgba(16, 17, 14, 0.66); + line-height: 1.65; +} + +.integration-doc > strong { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + margin-top: auto; + padding-top: 30px; + font-size: 0.75rem; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.integration-doc > strong i { + font-size: 1.2rem; + font-style: normal; +} + +.features-section { + padding: clamp(100px, 12vw, 180px) 0 clamp(110px, 13vw, 190px); + color: #fff; + background: var(--home-ink); +} + +.feature-type { + display: grid; + gap: 12px; + width: 106%; + margin-bottom: clamp(110px, 14vw, 200px); + margin-left: -3%; + color: transparent; + font-family: 'Space Grotesk', var(--vp-font-family-base); + font-size: clamp(3rem, 7vw, 7.5rem); + font-weight: 700; + letter-spacing: -0.055em; + line-height: 0.8; + text-transform: uppercase; + white-space: nowrap; + -webkit-text-stroke: 1px rgba(255, 255, 255, 0.2); + transform: rotate(-2deg) scale(1.02); +} + +.feature-type__lane { + overflow: hidden; +} + +.feature-type__track { + display: flex; + width: max-content; + animation: home-feature-scroll 52s linear infinite; + will-change: transform; +} + +.feature-type__lane--reverse .feature-type__track { + animation-direction: reverse; + animation-duration: 58s; +} + +.feature-type__group { + flex: 0 0 auto; + padding-right: 0.45em; +} + +.section-heading--features { + display: flex; + align-items: end; + justify-content: space-between; + gap: 40px; + margin-bottom: 72px; +} + +.section-heading--features h2 { + color: #fff; +} + +.section-heading--features p { + max-width: 420px; + padding-bottom: 8px; + color: rgba(255, 255, 255, 0.62); +} + +.hardware-features { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} + +.hardware-card { + position: relative; + display: flex; + overflow: hidden; + flex-direction: column; + min-height: 580px; + padding: 26px; + border: 1px solid rgba(255, 255, 255, 0.16); +} + +.hardware-card__copy { + position: relative; + z-index: 3; + margin-top: auto; +} + +.hardware-card h3, +.screen-feature h3 { + margin: 0 0 18px; + color: inherit; + font-family: 'Space Grotesk', var(--vp-font-family-base); + font-size: clamp(2.6rem, 4.5vw, 5rem); + font-weight: 700; + letter-spacing: -0.065em; + line-height: 0.82; + text-transform: uppercase; +} + +.hardware-card p, +.screen-feature p { + margin: 0; + color: inherit; + opacity: 0.7; + line-height: 1.65; +} + +.hardware-card a, +.screen-feature a { + display: inline-flex; + align-items: center; + gap: 16px; + margin-top: 24px; + padding-bottom: 4px; + border-bottom: 1px solid currentColor; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.hardware-card--rumble { + color: var(--home-ink); + background: var(--home-acid); +} + +.rumble-rings { + position: absolute; + top: 42%; + left: 50%; + width: 95%; + aspect-ratio: 1; + transform: translate(-50%, -50%); +} + +.rumble-rings span { + position: absolute; + top: 50%; + left: 50%; + border: 2px solid var(--home-ink); + border-radius: 50%; + transform: translate(-50%, -50%); + animation: home-rumble-ring 2.4s ease-out infinite; +} + +.rumble-rings span:nth-child(1) { width: 16%; height: 16%; animation-delay: 0s; } +.rumble-rings span:nth-child(2) { width: 40%; height: 40%; animation-delay: 0.16s; } +.rumble-rings span:nth-child(3) { width: 66%; height: 66%; animation-delay: 0.32s; } +.rumble-rings span:nth-child(4) { width: 92%; height: 92%; animation-delay: 0.48s; } + +.rumble-controller { + position: absolute; + z-index: 2; + top: 42%; + left: 50%; + width: 72%; + filter: drop-shadow(0 18px 12px rgba(16, 17, 14, 0.24)); + transform: translate(-50%, -50%); +} + +.rumble-controller img { + display: block; + width: 100%; + image-rendering: pixelated; +} + +.rumble-controller__moving { + animation: + home-rumble-jitter 80ms linear infinite, + home-rumble-active 3s step-end infinite; +} + +.rumble-controller__still { + position: absolute; + inset: 0; + opacity: 0; + animation: home-rumble-rest 3s step-end infinite; +} + +.hardware-card--gyro { + color: #fff; + background: var(--home-blue-dark); +} + +.gyro-display { + position: absolute; + top: 40%; + left: 50%; + width: 80%; + aspect-ratio: 1; + border: 1px dashed rgba(255, 255, 255, 0.26); + border-radius: 50%; + transform: translate(-50%, -50%); +} + +.gyro-display::before, +.gyro-display::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + background: rgba(255, 255, 255, 0.14); + transform: translate(-50%, -50%); +} + +.gyro-display::before { width: 120%; height: 1px; } +.gyro-display::after { width: 1px; height: 120%; } + +.gyro-display span { + position: absolute; + inset: 16%; + border: 1px solid rgba(238, 242, 86, 0.26); + border-radius: 50%; +} + +.gyro-display img { + position: absolute; + top: 50%; + left: 50%; + width: 68%; + image-rendering: pixelated; + transform: translate(-50%, -50%) rotate(-12deg); + animation: home-gyro-use 5.2s cubic-bezier(0.45, 0, 0.55, 1) infinite; +} + +.hardware-card--triggers { + color: #fff; + background: #090a08; +} + +.hardware-card--triggers::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(to bottom, rgba(16, 17, 14, 0.05) 20%, rgba(16, 17, 14, 0.97) 78%); +} + +.hardware-card--triggers > img { + position: absolute; + inset: 0; + width: 100%; + height: 58%; + object-fit: cover; +} + +.trigger-meter { + position: relative; + z-index: 3; + display: grid; + grid-template-columns: 28px repeat(8, 1fr); + gap: 4px; + height: 58px; + margin-top: 20px; + padding: 7px; + background: rgba(16, 17, 14, 0.82); + backdrop-filter: blur(8px); +} + +.trigger-meter span { + align-self: center; + font-family: var(--vp-font-family-mono); + font-size: 0.6rem; +} + +.trigger-meter i { + background: var(--home-acid); +} + +.trigger-meter i:nth-last-child(-n + 2) { + border: 1px solid rgba(255, 255, 255, 0.18); + background: transparent; +} + +.hardware-card--triggers .hardware-card__copy p { + color: rgba(255, 255, 255, 0.76); + opacity: 1; +} + +.screen-features { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 16px; + margin-top: 16px; +} + +.screen-feature { + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.16); + color: #fff; + background: #191a17; +} + +.screen-feature--radial, +.screen-feature--binds { + grid-column: span 7; +} + +.screen-feature--keyboard, +.screen-feature--guides { + grid-column: span 5; +} + +.screen-feature > img { + display: block; + width: 100%; + aspect-ratio: 16 / 8.6; + object-fit: cover; + border-bottom: 1px solid rgba(255, 255, 255, 0.16); + transition: transform 500ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.screen-feature:hover > img { + transform: scale(1.02); +} + +.screen-feature > div { + padding: 28px; +} + +.screen-feature h3 { + font-size: clamp(2.2rem, 4vw, 4.4rem); +} + +.screen-feature p { + max-width: 650px; + color: rgba(255, 255, 255, 0.68); + opacity: 1; +} + +.multiplayer-section { + padding: clamp(110px, 13vw, 190px) 0; + color: var(--home-ink); + background: var(--home-blue); +} + +.section-heading--multiplayer > div { + display: grid; + gap: 20px; + padding-bottom: 7px; +} + +.section-heading--multiplayer > div p { + color: rgba(16, 17, 14, 0.88); +} + +.multiplayer-facts { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 48px; +} + +.multiplayer-facts span { + padding: 9px 12px; + border: 1px solid var(--home-ink); + font-family: var(--vp-font-family-mono); + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.multiplayer-gallery { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + grid-template-rows: repeat(2, minmax(230px, 1fr)); + gap: 16px; + min-height: 720px; + margin-top: 64px; +} + +.game-shot--pit { + grid-column: span 7; + grid-row: 1 / -1; +} + +.game-shot--wall, +.game-shot--summit { + grid-column: span 5; +} + +.download-section { + position: relative; + overflow: hidden; + padding: clamp(110px, 15vw, 210px) 0; + text-align: center; + background: var(--home-acid); +} + +.download-section::before, +.download-section::after { + content: ''; + position: absolute; + width: 34vw; + max-width: 500px; + aspect-ratio: 1; + border: 1px solid rgba(16, 17, 14, 0.18); + border-radius: 50%; +} + +.download-section::before { top: -26%; left: -8%; } +.download-section::after { right: -8%; bottom: -32%; } + +.download-section__inner { + position: relative; + z-index: 2; + display: grid; + justify-items: center; +} + +.download-section__inner > img { + width: 128px; + height: 128px; + margin-bottom: 28px; + object-fit: contain; + image-rendering: pixelated; + filter: drop-shadow(10px 12px 0 rgba(16, 17, 14, 0.2)); + transform: rotate(7deg); +} + +.download-section p { + margin: 30px 0 0; + font-size: 1.08rem; +} + +.home-actions--centered { + justify-content: center; + margin-top: 42px; +} + +.home-button--dark { + color: #fff !important; + background: var(--home-ink); +} + +.home-button--outline-dark { + border-color: var(--home-ink); + color: var(--home-ink) !important; + background: transparent; +} + +.home-button--outline-dark:hover { + background: rgba(241, 242, 232, 0.44); +} + +@keyframes home-controller-float { + 0%, 100% { transform: translateY(0) rotate(-1deg); } + 50% { transform: translateY(-10px) rotate(1deg); } +} + +@keyframes home-gyro-use { + 0%, 100% { transform: translate(-50%, -50%) rotate(-7deg); } + 18% { transform: translate(-59%, -58%) rotate(-15deg); } + 42% { transform: translate(-41%, -55%) rotate(9deg); } + 65% { transform: translate(-44%, -40%) rotate(15deg); } + 84% { transform: translate(-58%, -44%) rotate(-11deg); } +} + +@keyframes home-rumble-jitter { + 0%, 100% { transform: translate(-3px, 1px) rotate(-0.4deg); } + 25% { transform: translate(2px, -2px) rotate(0.3deg); } + 50% { transform: translate(-1px, -1px) rotate(-0.2deg); } + 75% { transform: translate(3px, 2px) rotate(0.4deg); } +} + +@keyframes home-rumble-active { + 0%, 66.666% { opacity: 1; } + 66.667%, 100% { opacity: 0; } +} + +@keyframes home-rumble-rest { + 0%, 66.666% { opacity: 0; } + 66.667%, 100% { opacity: 1; } +} + +@keyframes home-controller-scroll { + to { transform: translateX(-50%); } +} + +@keyframes home-feature-scroll { + to { transform: translateX(-50%); } +} + +@keyframes home-rumble-ring { + 0%, 100% { opacity: 0.25; } + 45% { opacity: 1; } +} + +@media (max-width: 1050px) { + .home-hero { + min-height: auto; + padding-top: 132px; + } + + .home-hero__layout, + .controller-section__header, + .section-heading--split, + .section-heading--multiplayer { + grid-template-columns: 1fr; + } + + .controller-display { + width: min(100%, 620px); + margin: 26px auto 0; + } + + .integration-gallery, + .multiplayer-gallery { + grid-template-rows: auto; + min-height: 0; + } + + .game-shot--sodium, + .game-shot--pit { + grid-column: 1 / -1; + grid-row: auto; + aspect-ratio: 16 / 9; + } + + .game-shot--minimega-glide, + .game-shot--minimega-lobby, + .game-shot--wall, + .game-shot--summit { + grid-column: span 6; + aspect-ratio: 16 / 9; + } + + .hardware-features { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .hardware-card--triggers { + grid-column: 1 / -1; + } +} + +@media (max-width: 760px) { + .home-shell { + width: min(100% - 32px, 1320px); + } + + .home-hero { + padding-top: 116px; + } + + .home-hero h1 { + font-size: clamp(3rem, 15vw, 4.1rem); + line-height: 0.84; + } + + .home-hero h1 span { + -webkit-text-stroke-width: 1px; + } + + .home-hero__facts { + line-height: 1.7; + } + + .home-button { + width: 100%; + } + + .controller-display { + margin-top: 18px; + } + + .controller-section h2, + .section-heading h2, + .download-section h2 { + font-size: clamp(3.2rem, 15.5vw, 5.9rem); + line-height: 0.84; + } + + .section-heading--split h2 { + font-size: clamp(2.4rem, 12.25vw, 3.8rem); + } + + .controller-chip { + grid-template-columns: 76px auto; + min-width: 220px; + height: 96px; + } + + .controller-chip img { + width: 66px; + height: 66px; + } + + .integration-gallery, + .multiplayer-gallery, + .integration-docs, + .hardware-features, + .screen-features { + grid-template-columns: 1fr; + } + + .game-shot--sodium, + .game-shot--minimega-glide, + .game-shot--minimega-lobby, + .game-shot--pit, + .game-shot--wall, + .game-shot--summit, + .hardware-card--triggers, + .screen-feature--radial, + .screen-feature--keyboard, + .screen-feature--guides, + .screen-feature--binds { + grid-column: 1; + } + + .game-shot { + aspect-ratio: 16 / 10; + } + + .game-shot figcaption { + right: 10px; + bottom: 10px; + left: 10px; + } + + .game-shot figcaption span { + display: none; + } + + .integration-doc { + min-height: 310px; + padding: 24px; + } + + .feature-type { + margin-bottom: 100px; + } + + .section-heading--features { + display: grid; + } + + .hardware-card { + min-height: 520px; + padding: 22px; + } + + .hardware-card h3, + .screen-feature h3 { + font-size: clamp(2.7rem, 13.5vw, 4.8rem); + } + + .screen-feature > div { + padding: 22px; + } + + .multiplayer-facts { + display: grid; + } + + .home-actions--centered { + width: 100%; + max-width: 360px; + } +} + +@media (prefers-reduced-motion: reduce) { + .controlify-home *, + .controlify-home *::before, + .controlify-home *::after { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 000000000..9b8c35a38 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,22 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import ControllerCompatibility from './components/ControllerCompatibility.vue' +import ControlifyHome from './components/ControlifyHome.vue' +import './style.css' +import './docs.css' +import './home.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + app.component('ControllerCompatibility', ControllerCompatibility) + app.component('ControlifyHome', ControlifyHome) + } +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 000000000..8f774b4c7 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,39 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap'); + +:root { + --vp-font-family-base: 'DM Sans', ui-sans-serif, system-ui, sans-serif; + --vp-font-family-mono: 'SFMono-Regular', Consolas, monospace; + --vp-c-brand-1: #716f00; + --vp-c-brand-2: #8f8c00; + --vp-c-brand-3: #e4e83f; + --vp-c-brand-soft: rgba(218, 224, 49, 0.2); + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); +} + +.dark { + --vp-c-bg: #10100d; + --vp-c-bg-alt: #171711; + --vp-c-bg-soft: #1d1d16; + --vp-c-bg-elv: #24241b; + --vp-c-brand-1: #f3f67b; + --vp-c-brand-2: #e6ea50; + --vp-c-brand-3: #d5da31; + --vp-c-brand-soft: rgba(235, 239, 77, 0.16); +} + +h1, +h2, +h3, +.VPNavBarTitle { + font-family: 'Space Grotesk', var(--vp-font-family-base); + letter-spacing: -0.025em; +} + +.VPNavBarTitle .logo { + width: 30px; + height: 30px; + border-radius: 8px; +} diff --git a/docs/_meta.json b/docs/_meta.json deleted file mode 100644 index 2bfa26518..000000000 --- a/docs/_meta.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "users": { - "name": "Users", - "icon": "User" - }, - "resource-packs": { - "name": "Resource Packs", - "icon": "Folder" - }, - "developers": { - "name": "Developers", - "icon": "CodeXml" - }, - "reference": { - "name": "Reference", - "icon": "BookMarked" - }, - "architecture": { - "name": "Architecture", - "icon": "Cog" - } -} diff --git a/docs/architecture/_meta.json b/docs/architecture/_meta.json deleted file mode 100644 index cd80d723a..000000000 --- a/docs/architecture/_meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "controllers.mdx": "Controllers" -} diff --git a/docs/bun.lock b/docs/bun.lock new file mode 100644 index 000000000..77a77e256 --- /dev/null +++ b/docs/bun.lock @@ -0,0 +1,463 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "docs", + "devDependencies": { + "@types/bun": "latest", + "vitepress": "^2.0.0-alpha.19", + "vue": "^3.5.41", + "wrangler": "^4.120.1", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="], + + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="], + + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260804.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-191/PPEFicRK2wK69eXzSjnLgHL79k7zR2VUpyIr8rhFgGns1b5bTHgnBuUrgU2LPbEtwbE5eL2hJ0uhLAFEow=="], + + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260804.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aI2cAFLsrNkSz3kLSQrgrO9ICTfY7jb2h0jgaWDE9mQLQQDfpXeYrKWt07tTgMmeNP79o9w3NZe3aqtt8mTGHQ=="], + + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260804.1", "", { "os": "linux", "cpu": "x64" }, "sha512-KBCjxBIlN2jucfQGaTK4EgmPsWzQgYR/zYhPfi3mkWwdoTyG1dgrt2aizKps/SYse85ci/SOxojKk0/K7vstPw=="], + + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260804.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-7lswfarBZ7xkHpTFrNb74ExwOltIalVaASc+GOYfCNtnwFxK/JZSVByfm/hnZEBtrHU7fMY2z7dYT64rwS0pHg=="], + + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260804.1", "", { "os": "win32", "cpu": "x64" }, "sha512-GOgRWYtxISN5rAWfx3K7zubt3xEn0/ZPFrbcLL+GwT4ouCKyNoHqfT1cPNB6Nx7t8BHEjnuTG7gkHO4Wd5ORdg=="], + + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + + "@docsearch/css": ["@docsearch/css@4.7.0", "", {}, "sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw=="], + + "@docsearch/js": ["@docsearch/js@4.7.0", "", {}, "sha512-x5lCqu1tetgsJFkjQ6VSocbHldsRkGEgwg5N98Vx21sq/V5wcmj4u226PY9k+TEpIgQ772zlYbPLTPicWyGnpA=="], + + "@docsearch/sidepanel-js": ["@docsearch/sidepanel-js@4.7.0", "", {}, "sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + + "@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.93", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-/XhANjfGYOuqvSR3TmUnkQkINvQ4GVjVuukvymRbxtVFBvIq/yiXJqCDycKcQPT401OYT9H2vIY6ihAlz1QIAw=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.1" }, "os": "darwin", "cpu": "arm64" }, "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.1" }, "os": "darwin", "cpu": "x64" }, "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw=="], + + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.2", "", { "dependencies": { "@img/sharp-wasm32": "0.35.2" }, "os": "freebsd" }, "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.1", "", { "os": "linux", "cpu": "none" }, "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.1" }, "os": "linux", "cpu": "arm" }, "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.1" }, "os": "linux", "cpu": "arm64" }, "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.1" }, "os": "linux", "cpu": "ppc64" }, "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.1" }, "os": "linux", "cpu": "none" }, "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.1" }, "os": "linux", "cpu": "s390x" }, "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.1" }, "os": "linux", "cpu": "x64" }, "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" }, "os": "linux", "cpu": "arm64" }, "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.1" }, "os": "linux", "cpu": "x64" }, "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.2", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw=="], + + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.2", "", { "dependencies": { "@img/sharp-wasm32": "0.35.2" }, "cpu": "none" }, "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.2", "", { "os": "win32", "cpu": "x64" }, "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.143.0", "", {}, "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA=="], + + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.3", "", { "os": "android", "cpu": "arm64" }, "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.3", "", { "os": "none", "cpu": "arm64" }, "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@shikijs/core": ["@shikijs/core@4.4.3", "", { "dependencies": { "@shikijs/primitive": "4.4.3", "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" } }, "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.4.3", "", { "dependencies": { "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.4.3", "", { "dependencies": { "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w=="], + + "@shikijs/langs": ["@shikijs/langs@4.4.3", "", { "dependencies": { "@shikijs/types": "4.4.3" } }, "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.4.3", "", { "dependencies": { "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ=="], + + "@shikijs/themes": ["@shikijs/themes@4.4.3", "", { "dependencies": { "@shikijs/types": "4.4.3" } }, "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw=="], + + "@shikijs/transformers": ["@shikijs/transformers@4.4.3", "", { "dependencies": { "@shikijs/core": "4.4.3", "@shikijs/types": "4.4.3" } }, "sha512-oJSARV6NaWd+rnNJbtnpAdj3Zg0ZVyzsnMgb3vi3HA+35y8lBWUCpOnWsmyiXZIikY+x1BDqrQUgmxfzWh7Jvw=="], + + "@shikijs/types": ["@shikijs/types@4.4.3", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.24", "", {}, "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + + "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], + + "@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="], + + "@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + + "@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="], + + "@vueuse/integrations": ["@vueuse/integrations@14.4.0", "", { "dependencies": { "@vueuse/core": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "async-validator": "^4", "axios": "^1", "change-case": "^5", "drauu": "^0.4", "focus-trap": "^7 || ^8", "fuse.js": "^7", "idb-keyval": "^6", "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", "universal-cookie": "^7 || ^8", "vue": "^3.5.0" }, "optionalPeers": ["async-validator", "axios", "change-case", "drauu", "focus-trap", "fuse.js", "idb-keyval", "jwt-decode", "nprogress", "qrcode", "sortablejs", "universal-cookie"] }, "sha512-oJz9qTgczvA7L1nXQFRU7h8tQbOCoiceqvMMhT9XYMyOGTqLJ2rEa09PON+nD2t48sZUfeOmg4eaWJXV4sZb/w=="], + + "@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="], + + "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "focus-trap": ["focus-trap@8.2.2", "", { "dependencies": { "tabbable": "^6.5.0" } }, "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "miniflare": ["miniflare@5.20260804.0-alpha", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.35.2", "undici": "7.29.0", "workerd": "1.20260804.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" } }, "sha512-UDegnTukIpJCI/lNyQXU7B8kYFLcMZihlxqqOocnc62tV8AO6XofmFmZm3ZLDXTWZpkZnLLJ2sKCSlTIEXO3dQ=="], + + "minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="], + + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "sharp": ["sharp@0.35.2", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.4" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.2", "@img/sharp-darwin-x64": "0.35.2", "@img/sharp-freebsd-wasm32": "0.35.2", "@img/sharp-libvips-darwin-arm64": "1.3.1", "@img/sharp-libvips-darwin-x64": "1.3.1", "@img/sharp-libvips-linux-arm": "1.3.1", "@img/sharp-libvips-linux-arm64": "1.3.1", "@img/sharp-libvips-linux-ppc64": "1.3.1", "@img/sharp-libvips-linux-riscv64": "1.3.1", "@img/sharp-libvips-linux-s390x": "1.3.1", "@img/sharp-libvips-linux-x64": "1.3.1", "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", "@img/sharp-libvips-linuxmusl-x64": "1.3.1", "@img/sharp-linux-arm": "0.35.2", "@img/sharp-linux-arm64": "0.35.2", "@img/sharp-linux-ppc64": "0.35.2", "@img/sharp-linux-riscv64": "0.35.2", "@img/sharp-linux-s390x": "0.35.2", "@img/sharp-linux-x64": "0.35.2", "@img/sharp-linuxmusl-arm64": "0.35.2", "@img/sharp-linuxmusl-x64": "0.35.2", "@img/sharp-webcontainers-wasm32": "0.35.2", "@img/sharp-win32-arm64": "0.35.2", "@img/sharp-win32-ia32": "0.35.2", "@img/sharp-win32-x64": "0.35.2" } }, "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w=="], + + "shiki": ["shiki@4.4.3", "", { "dependencies": { "@shikijs/core": "4.4.3", "@shikijs/engine-javascript": "4.4.3", "@shikijs/engine-oniguruma": "4.4.3", "@shikijs/langs": "4.4.3", "@shikijs/themes": "4.4.3", "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + + "tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], + + "vitepress": ["vitepress@2.0.0-alpha.19", "", { "dependencies": { "@docsearch/css": "^4.7.0", "@docsearch/js": "^4.7.0", "@docsearch/sidepanel-js": "^4.7.0", "@iconify-json/simple-icons": "^1.2.92", "@shikijs/core": "^4.4.1", "@shikijs/transformers": "^4.4.1", "@shikijs/types": "^4.4.1", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^6.0.8", "@vue/devtools-api": "^8.2.1", "@vue/shared": "^3.5.40", "@vueuse/core": "^14.4.0", "@vueuse/integrations": "^14.4.0", "focus-trap": "^8.2.2", "mark.js": "8.11.1", "minisearch": "^7.2.0", "shiki": "^4.4.1", "vite": "^8.2.0", "vue": "^3.5.40" }, "peerDependencies": { "markdown-it-mathjax3": "^4", "postcss": "^8" }, "optionalPeers": ["markdown-it-mathjax3", "postcss"], "bin": { "vitepress": "bin/vitepress.js" } }, "sha512-WnBsb0Bwr43kXKyiis+lld/7ri3hnMbthS8N3hpFtjjwsdLO4IRmiAE08D7aud4q6oMDf9uwRowxzNqRFe/amw=="], + + "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="], + + "workerd": ["workerd@1.20260804.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260804.1", "@cloudflare/workerd-darwin-arm64": "1.20260804.1", "@cloudflare/workerd-linux-64": "1.20260804.1", "@cloudflare/workerd-linux-arm64": "1.20260804.1", "@cloudflare/workerd-windows-64": "1.20260804.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-b0P38g5/ssemwWxd/mafNYggEZ0ere7PCiUH6RCHkgqjRhhXTP45nDiL2L3iIvi8uF2IjNrGpVgMXEunCaeY/w=="], + + "wrangler": ["wrangler@4.120.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20260804.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260804.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260804.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-rQJ38rY02XiVITbSBWHC7oap3M2evcXgsC4CdlIX4WQENUZMMnue9j2vXO4aIi39KR+jDH+UHK8JNqp1+UjkRQ=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + } +} diff --git a/docs/architecture/controllers.mdx b/docs/content/architecture/controllers.md similarity index 99% rename from docs/architecture/controllers.mdx rename to docs/content/architecture/controllers.md index b7c4400ee..8c7282bce 100644 --- a/docs/architecture/controllers.mdx +++ b/docs/content/architecture/controllers.md @@ -2,6 +2,8 @@ title: Controllers --- +# Controllers + You may be wondering how Controlify actually works. This page will explain how Controlify handles and uses controllers internally. diff --git a/docs/content/changelog/3.4.1.md b/docs/content/changelog/3.4.1.md new file mode 100644 index 000000000..d11ab9fdf --- /dev/null +++ b/docs/content/changelog/3.4.1.md @@ -0,0 +1,14 @@ +# Controlify 3.4.1 + +This version has the following targets: +- 26.1 +- 26.2 + +**By donating on my [Patreon](https://patreon.com/isxander), you will gain access to builds of Controlify for splitscreen support and snapshot versions** + +## Changes + +- Re-add and deprecate old code-based radial candidate api +- Make the hotbar select radial page show the name of the item instead of the slot number +- Fix item models that use range_dispatch on damage from using the fallback model +- Fix binding categories being untranslated keys diff --git a/docs/developers/adaptive-trigger-api.mdx b/docs/content/developers/adaptive-trigger-api.md similarity index 98% rename from docs/developers/adaptive-trigger-api.mdx rename to docs/content/developers/adaptive-trigger-api.md index eb0f48111..122d6b39d 100644 --- a/docs/developers/adaptive-trigger-api.mdx +++ b/docs/content/developers/adaptive-trigger-api.md @@ -2,6 +2,8 @@ title: Adaptive Trigger API --- +# Adaptive Trigger API + Resource packs should be used for adaptive trigger effects based on item identities, tags, or serialized data-component values. The Java API is available when an effect needs custom code or cannot be expressed with resource-pack matching. diff --git a/docs/developers/bindings-api.mdx b/docs/content/developers/bindings-api.md similarity index 83% rename from docs/developers/bindings-api.mdx rename to docs/content/developers/bindings-api.md index 1c2062184..76fd0f7e0 100644 --- a/docs/developers/bindings-api.mdx +++ b/docs/content/developers/bindings-api.md @@ -2,14 +2,16 @@ title: Bindings API --- +# Bindings API + _Learn how to register new controller bindings._ ## Registering a custom input binding - - Register bindings inside the Controlify pre-init entrypoint. Read more in - [Controlify Entrypoint](controlify-entrypoint#using-the-entrypoint). - +::: info +Register bindings inside the Controlify pre-init entrypoint. Read more in +[Controlify Entrypoint](controlify-entrypoint#using-the-entrypoint). +::: Controlify bindings let users assign controller inputs to actions in your mod. Register a binding through the `ControlifyBindApi` supplied by the pre-init context: @@ -83,8 +85,8 @@ and resource-pack override behavior. Default controller inputs are data-driven. To assign the south face button to the example binding for every controller, create: - -```json !!tabs assets/controlify/controllers/default_bind/default.json +::: code-group +```json [assets/controlify/controllers/default_bind/default.json] { "defaults": { "mymod:action1": { @@ -93,7 +95,7 @@ controller, create: } } ``` - +::: Defaults for a specific controller namespace belong at `assets//controllers/default_bind/.json`. For the full format, see @@ -103,17 +105,17 @@ Defaults for a specific controller namespace belong at Once you have an `InputBinding`, its commonly used state accessors include: -| Property | Description | -|----------------------|--------------------------------------------------------------------------------------------------| -| `inputGlyph()` | Returns the input glyph for this binding and controller. | -| `digitalNow()` | Returns whether the binding is pressed this tick. | -| `digitalPrev()` | Returns whether the binding was pressed during the previous tick. | -| `analogueNow()` | Returns the current analogue value, from 0 to 1. | -| `analoguePrev()` | Returns the analogue value from the previous tick. | -| `justPressed()` | Returns whether the binding became pressed this tick. | -| `justReleased()` | Returns whether the binding became released this tick. | -| `justTapped()` | Returns whether the binding was pressed and then released. | -| `guiPressed().get()` | Handles a GUI press that completes only if focus remains on the widget where the press began. | +| Property | Description | +|----------------------|-----------------------------------------------------------------------------------------------| +| `inputGlyph()` | Returns the input glyph for this binding and controller. | +| `digitalNow()` | Returns whether the binding is pressed this tick. | +| `digitalPrev()` | Returns whether the binding was pressed during the previous tick. | +| `analogueNow()` | Returns the current analogue value, from 0 to 1. | +| `analoguePrev()` | Returns the analogue value from the previous tick. | +| `justPressed()` | Returns whether the binding became pressed this tick. | +| `justReleased()` | Returns whether the binding became released this tick. | +| `justTapped()` | Returns whether the binding was pressed and then released. | +| `guiPressed().get()` | Handles a GUI press that completes only if focus remains on the widget where the press began. | See `InputBinding` in the API sources for advanced state access and custom outputs. diff --git a/docs/developers/controlify-entrypoint.mdx b/docs/content/developers/controlify-entrypoint.md similarity index 74% rename from docs/developers/controlify-entrypoint.mdx rename to docs/content/developers/controlify-entrypoint.md index ed59c2a0e..f38bcb164 100644 --- a/docs/developers/controlify-entrypoint.mdx +++ b/docs/content/developers/controlify-entrypoint.md @@ -2,6 +2,8 @@ title: Controlify Entrypoint --- +# Controlify Entrypoint + _Learn how to hook into Controlify._ Controlify provides a Fabric entrypoint to hook into important lifecycle stages of Controlify. You do this just like `ClientModInitializer`. @@ -11,21 +13,21 @@ Controlify provides a Fabric entrypoint to hook into important lifecycle stages - On Fabric, you register an entrypoint like any other, by adding an entry to your `fabric.mod.json` file under the `entrypoints` section. - On NeoForge (or Fabric), you register an entrypoint using a Java service provider interface (SPI) in your `META-INF/services` directory. - - ```json !!tabs (Fabric) fabric.mod.json - { - "entrypoints": { - "controlify": [ - "com.example.mymod.ControlifyEntrypoint" - ] - } +::: code-group +```json [fabric.mod.json] +{ + "entrypoints": { + "controlify": [ + "com.example.mymod.ControlifyEntrypoint" + ] } - ``` +} +``` - ```txt !!tabs (NeoForge/Universal) META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint - com.example.mymod.MyControlifyEntrypoint - ``` - +```txt [META-INF/services/dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint] +com.example.mymod.MyControlifyEntrypoint +``` +::: Then simply create the class and implement the interface: diff --git a/docs/developers/getting-started.mdx b/docs/content/developers/getting-started.md similarity index 98% rename from docs/developers/getting-started.mdx rename to docs/content/developers/getting-started.md index 69c3e304c..8888dbef2 100644 --- a/docs/developers/getting-started.mdx +++ b/docs/content/developers/getting-started.md @@ -2,6 +2,8 @@ title: Getting Started --- +# Getting Started + _Learn how to depend on Controlify._ ## Adding Controlify to your gradle project diff --git a/docs/developers/guide-api.mdx b/docs/content/developers/guide-api.md similarity index 95% rename from docs/developers/guide-api.mdx rename to docs/content/developers/guide-api.md index 5bf0cbbb2..09711d912 100644 --- a/docs/developers/guide-api.mdx +++ b/docs/content/developers/guide-api.md @@ -2,12 +2,14 @@ title: Guide API --- +# Guide API + _Learn how to use existing Guide domains, and register your own, for use in your own Screens._ - - For instructions on modifying rules for existing Guide domains, - see the [resource pack page](../resource-packs/guides) - +::: info +For instructions on modifying rules for existing Guide domains, +see the [resource pack page](../resource-packs/guides) +::: ## How does this API work? diff --git a/docs/developers/screen-operation-api.mdx b/docs/content/developers/screen-operation-api.md similarity index 93% rename from docs/developers/screen-operation-api.mdx rename to docs/content/developers/screen-operation-api.md index 14dc4c7d7..d5ee8e5bc 100644 --- a/docs/developers/screen-operation-api.mdx +++ b/docs/content/developers/screen-operation-api.md @@ -2,11 +2,13 @@ title: Screen Operation API --- +# Screen Operation API + _Add controller compatibility to GUIs._ You can add explicit controller support to your UI if necessary with the various APIs in Controlify. -To make sure that your GUI classes don't _depend_ on Controlify to load, all controller handling is done in a separate class, namely the `ScreenProcessor`, that is registered in [Controlify's pre-init entrypoint](../controlify-entrypoint#pre-init-method). +To make sure that your GUI classes don't _depend_ on Controlify to load, all controller handling is done in a separate class, namely the `ScreenProcessor`, that is registered in [Controlify's pre-init entrypoint](controlify-entrypoint#using-the-entrypoint). ## Making a screen @@ -153,9 +155,9 @@ public class MyAmazingScreenMixin implements ScreenProcessorProvider { } ``` - - Make sure to store a reference to the processor in a field, and not initialise it inside the getter function. - +::: warning +Make sure to store a reference to the processor in a field, and not initialise it inside the getter function. +::: It is not inherently bad to mixin into your own classes, so don't feel that this in an 'improper' way. However using the registry is probably better. This method is useful when adding processors to vanilla screens, where you have easier access to fields and functions with `@Shadow` that you can pass into your processor. @@ -210,9 +212,9 @@ public record AbstractButtonProcessor(AbstractButton button) implements Componen Here we added a constructor so we have access to the button, removed the unneeded overrides and added yet another quick binding check to press the button. Again, this can be switched out for your own binding that you created in [the other wiki page](bindings-api). - - The use case of a component processor shown above will actually not behave any differently to if it wasn't there. This is because if there is no component processor, `GUI_PRESS` will just emulate the enter key, which buttons already handle for. However, it is always best to have explicit compatibility to not rely on emulation. - +::: info +The use case of a component processor shown above will actually not behave any differently to if it wasn't there. This is because if there is no component processor, `GUI_PRESS` will just emulate the enter key, which buttons already handle for. However, it is always best to have explicit compatibility to not rely on emulation. +::: ## Attaching a `ComponentProcessor` @@ -251,9 +253,9 @@ public class AbstractButtonMixin implements ComponentProcessorProvider { } ``` - - Make sure to store a reference to the processor in a field, and not initialise it inside the getter function. - +::: warning +Make sure to store a reference to the processor in a field, and not initialise it inside the getter function. +::: ## Using button guides diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 000000000..a9eb1d890 --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,9 @@ +--- +layout: home +markdownStyles: false +pageClass: controlify-home-page +title: Controlify | Controller support for Minecraft Java Edition +description: Use a controller in Minecraft Java Edition with full menu navigation, mod support, multiplayer, rumble, gyro, adaptive triggers and remappable controls. +--- + + diff --git a/docs/reference/builtin-bindings.mdx b/docs/content/reference/builtin-bindings.md similarity index 99% rename from docs/reference/builtin-bindings.mdx rename to docs/content/reference/builtin-bindings.md index feeb322ee..fb1af02da 100644 --- a/docs/reference/builtin-bindings.mdx +++ b/docs/content/reference/builtin-bindings.md @@ -2,6 +2,8 @@ title: Built-in Bindings --- +# Built-in Bindings + This is a list of all Controlify's built-in bindings with brief descriptions and availability. These are the entries on the 'Controls' page of controller settings. diff --git a/docs/reference/builtin-controller-namespaces.mdx b/docs/content/reference/builtin-controller-namespaces.md similarity index 96% rename from docs/reference/builtin-controller-namespaces.mdx rename to docs/content/reference/builtin-controller-namespaces.md index 4822b0982..0f0bc6398 100644 --- a/docs/reference/builtin-controller-namespaces.mdx +++ b/docs/content/reference/builtin-controller-namespaces.md @@ -2,6 +2,8 @@ title: Built-in controller namespaces --- +# Built-in controller namespaces + Controlify already includes some built-in controller namespaces for common controllers. For more info on what controller namespaces are, see the diff --git a/docs/reference/builtin-inputs.mdx b/docs/content/reference/builtin-inputs.md similarity index 82% rename from docs/reference/builtin-inputs.mdx rename to docs/content/reference/builtin-inputs.md index 4635f0d72..5f161dc01 100644 --- a/docs/reference/builtin-inputs.mdx +++ b/docs/content/reference/builtin-inputs.md @@ -2,6 +2,8 @@ title: Built-in Inputs --- +# Built-in Inputs + For each controller input, like pressing the A button, or pushing up on the left stick, Controlify has a unique identifier that uses Minecraft's resource location system. @@ -50,11 +52,11 @@ Most of these remain unused by most controller drivers. - `controlify:button/misc_6` ### Paddles - - Some controllers with paddles, like the Xbox Elite controllers, do not work with this. - Their paddles are mapped to other buttons in the firmware of the controller. No data of - paddles ever reaches the connected computer. - +::: info +Some controllers with paddles, like the Xbox Elite controllers, do not work with this. +Their paddles are mapped to other buttons in the firmware of the controller. No data of +paddles ever reaches the connected computer. +::: - `controlify:button/right_paddle_1` - `controlify:button/right_paddle_2` @@ -84,7 +86,7 @@ So for index 0, it would be `controlify:button/0`. So for index 0, pushing in the positive direction, it would be `controlify:axis/0/positive`. - - Controlify axes operate within a range of [0, 1], not [-1, 1]. Hence, each axis is split into two inputs: - one for the positive direction, and one for the negative direction. - +::: info +Controlify axes operate within a range of [0, 1], not [-1, 1]. Hence, each axis is split into two inputs: +one for the positive direction, and one for the negative direction. +::: diff --git a/docs/resource-packs/adaptive-trigger-effects.mdx b/docs/content/resource-packs/adaptive-trigger-effects.md similarity index 99% rename from docs/resource-packs/adaptive-trigger-effects.mdx rename to docs/content/resource-packs/adaptive-trigger-effects.md index 10d768294..b2da28bb4 100644 --- a/docs/resource-packs/adaptive-trigger-effects.mdx +++ b/docs/content/resource-packs/adaptive-trigger-effects.md @@ -2,6 +2,8 @@ title: Adaptive Trigger Effects --- +# Adaptive Trigger Effects + Controlify's DualSense adaptive trigger effects can be changed with a resource pack. Effects are applied to the controller input bound to either **Use Item** or **Attack**. diff --git a/docs/resource-packs/custom-controller-identification.mdx b/docs/content/resource-packs/custom-controller-identification.md similarity index 90% rename from docs/resource-packs/custom-controller-identification.mdx rename to docs/content/resource-packs/custom-controller-identification.md index a35f0c555..3a577647a 100644 --- a/docs/resource-packs/custom-controller-identification.mdx +++ b/docs/content/resource-packs/custom-controller-identification.md @@ -2,6 +2,8 @@ title: Custom Controller Identification --- +# Custom Controller Identification + ## What is it? Controlify allows you to create custom definitions for your controller. @@ -10,7 +12,7 @@ Creating a custom definition allows you to define a **specific name** for your c **a sprite texture** (like the ones you see in the controller select screen), [**default bindings**](default-binds) specific to your controller, an [**input glyph set**](input-glyphs), -and [**special mappings**](input-mappings) for controllers like flight-sticks. +and special input mappings for controllers like flight sticks. ## Creating the definition @@ -19,11 +21,11 @@ Unlike most resources in Minecraft, this file is **additive** and **does not ove provided by Controlify. All resource packs are loaded and their definitions are *added* to the definition list. - - Ensure the file extension is **.json5**, and NOT `.json`. - If you don't know what JSON5 is, don't worry, it's compatible with regular json, - so use it as you normally would JSON. - +::: info +Ensure the file extension is **.json5**, and NOT `.json`. +If you don't know what JSON5 is, don't worry, it's compatible with regular json, +so use it as you normally would JSON. +::: Here is a definition for Switch Pro controllers. ```json5 diff --git a/docs/resource-packs/default-binds.mdx b/docs/content/resource-packs/default-binds.md similarity index 98% rename from docs/resource-packs/default-binds.mdx rename to docs/content/resource-packs/default-binds.md index e133c3716..bf1817921 100644 --- a/docs/resource-packs/default-binds.mdx +++ b/docs/content/resource-packs/default-binds.md @@ -2,6 +2,8 @@ title: Default Binds --- +# Default Binds + ## What is it? The default binding of the `Controls` found in controller settings is completely data-driven. diff --git a/docs/resource-packs/guides.mdx b/docs/content/resource-packs/guides.md similarity index 96% rename from docs/resource-packs/guides.mdx rename to docs/content/resource-packs/guides.md index e470db7df..5002e3d12 100644 --- a/docs/resource-packs/guides.mdx +++ b/docs/content/resource-packs/guides.md @@ -2,15 +2,20 @@ title: Guides --- +# Guides + ## What are guides? Guides are a feature of Controlify that shows hints to the player about what buttons to press to perform certain actions. Since version `2.3.0`, guides are data-driven, meaning you can override the default guides with your own. -
- - +
+ +![The in-game button guide](/images/in-game-guide.webp) + +![The container button guide](/images/container-guide.webp) +
## Concepts @@ -37,8 +42,9 @@ Controlify only allows resource packs to add and override _rules_, not _facts_ o If Controlify does not have a _fact_ for your specific use case, you must create a mod that adds the _fact_ to Controlify, or submit a feature request to get it added to Controlify itself. - -```json !!tabs assets/controlify/guides/in_game.json + +::: code-group +```json [assets/controlify/guides/in_game.json] { "override": false, "rules": [ @@ -52,7 +58,7 @@ or submit a feature request to get it added to Controlify itself. ] } ``` - +::: This example shows a _rule_ that applies when the player is on the ground, and the `controlify:jump` binding is bound. When the _rule_ applies, it shows the text "Jump" and the glyph for the `controlify:jump` binding. @@ -71,8 +77,8 @@ If you want to override the default rules, or any resource pack below yours, set You can stack multiple rules for the same binding, the first rule that succeeds will be applied, the rest will be ignored. However, if the rules have different locations (e.g. `left` and `right`), they will both be applied. - -```json !!tabs assets/controlify/guides/in_game.json +::: code-group +```json [assets/controlify/guides/in_game.json] { "override": false, "rules": [ @@ -93,7 +99,7 @@ However, if the rules have different locations (e.g. `left` and `right`), they w ] } ``` - +::: In this snippet, the first rule will apply when the player is in water, and the second rule will apply when the player is on the ground. @@ -165,4 +171,3 @@ These facts are available in all domains. | `controlify:cursor_outside_container` | When the user is hovering their cursor outside the container interface. | | `controlify:hovering_item_is_bundle` | When the user is hovering their cursor over a slot which is occupied with an item tagged as a bundle. | | `controlify:selected_bundle_slot` | When the user is currently selecting an item from within the bundle they're hovering. | - diff --git a/docs/resource-packs/input-glyphs.mdx b/docs/content/resource-packs/input-glyphs.md similarity index 79% rename from docs/resource-packs/input-glyphs.mdx rename to docs/content/resource-packs/input-glyphs.md index 5459cd93d..fcd3e8b5a 100644 --- a/docs/resource-packs/input-glyphs.mdx +++ b/docs/content/resource-packs/input-glyphs.md @@ -2,14 +2,14 @@ title: Input Glyphs --- +# Input Glyphs + ## What is it? Input glyphs are the icons that represent the buttons on your controller. Controlify has many built-in glyph sets for common controllers, but you can also create your own. -
- -
+![A controller button glyph shown beside a Controlify option](/images/options-button-with-glyph.webp) These glyphs are actually their own font, and you create them the same way as any other font in a resource pack. All you need to turn it into a controller glyph set is one extra file in the resource pack to link it together. @@ -18,15 +18,15 @@ All you need to turn it into a controller glyph set is one extra file in the res ### 1. Create the font file - - **Refer to the [Minecraft Wiki](https://minecraft.wiki/w/Font#Providers) for information on creating fonts.** - This wiki will help you understand how to create a font file and how all the providers work. - +::: tip +**Refer to the [Minecraft Wiki](https://minecraft.wiki/w/Font#Providers) for information on creating fonts.** +This wiki will help you understand how to create a font file and how all the providers work. +::: To create a glpyh set for the controller namespace `my_resource_pack:switch`, create a font file in your resource pack, like `/assets/my_resource_pack/font/controller/switch.json`. -```json5 +```jsonc { "providers": [ { @@ -53,9 +53,7 @@ As per the `"file"` entry in your font file, create a texture file in your resou In this case, `assets/my_resource_pack/textures/font/controller/switch.png`. -
- -
+![An example controller button glyph atlas](/images/button-glyph-atlas.webp) ### 3. Create the glyph font mapping diff --git a/docs/resource-packs/keyboard-layouts.mdx b/docs/content/resource-packs/keyboard-layouts.md similarity index 97% rename from docs/resource-packs/keyboard-layouts.mdx rename to docs/content/resource-packs/keyboard-layouts.md index 13197906c..2b760a5a2 100644 --- a/docs/resource-packs/keyboard-layouts.mdx +++ b/docs/content/resource-packs/keyboard-layouts.md @@ -2,6 +2,8 @@ title: Keyboard Layouts --- +# Keyboard Layouts + Design, data format, and examples for Controlify's on‑screen keyboard layouts. ## What is it? @@ -15,9 +17,9 @@ Layouts are discovered from resource packs and selected per language with a fall - Built‑ins: `controlify:full`, `controlify:simple`, `controlify:server_ip` (see KeyboardLayouts) - Fallback: if a requested layout can’t be found/parsed, an internal QWERTY fallback is used - +::: warning Row widths are validated. If any row’s key widths don’t add up to the layout width, the file won’t load. - +::: ## File format Top level @@ -52,10 +54,10 @@ A KeyFunction that sends one or more key codes ([GLFW/Minecraft `InputConstants` A key code is a numeric identifier for a key on a keyboard, such as `GLFW_KEY_A (65)` or `GLFW_KEY_ENTER (257)`. They represent a physical key on your keyboard, rather than a printable character. - +::: info Common key codes used in keyboards can be replaced with [special actions](#special-actions) for better readability and localisation benefits. - +::: It can be specified in two forms: @@ -153,9 +155,9 @@ Switch to another layout and back - Paste inserts clipboard text as characters; Copy‑all calls the target’s copy handler. - When changing layouts, focus is preserved using `identifier` if possible, or by focusing a `previous_layout` key. - +::: info If you only provide a single KeyFunction (e.g. a string), the key uses that for both layers, with the string’s uppercase form as the shifted display by default. - +::: ## Common conventions @@ -179,6 +181,6 @@ Add the following to the top of your keyboard layout JSON file to get schema val ```json { - "$schema": "https://erebus.moddedmc.wiki/api/v1/docs/controlify/asset/controlify:schemas/keyboard_layout" + "$schema": "https://controlify.isxander.dev/schemas/keyboard_layout.json" } ``` diff --git a/docs/resource-packs/radial-icons.mdx b/docs/content/resource-packs/radial-icons.md similarity index 94% rename from docs/resource-packs/radial-icons.mdx rename to docs/content/resource-packs/radial-icons.md index 65b5238b5..aa29cbd83 100644 --- a/docs/resource-packs/radial-icons.mdx +++ b/docs/content/resource-packs/radial-icons.md @@ -2,6 +2,8 @@ title: Radial Menu Icons --- +# Radial Menu Icons + Radial menu candidates and their icons are defined by resource packs. Mods can provide an icon for one of their bindings by including resources in their own JAR; no Java call into Controlify is required. @@ -10,8 +12,7 @@ their bindings by including resources in their own JAR; no Java call into Contro Create `assets/controlify/radial_icons.json`. The keys are binding IDs and each value defines either an item model or a GUI sprite: - -```json !!tabs assets/controlify/radial_icons.json +```json [assets/controlify/radial_icons.json] { "example:open_backpack": { "model": "example:backpack" @@ -24,7 +25,6 @@ model or a GUI sprite: } } ``` - An entry must contain exactly one of `model` or `texture`. Adding a binding to this file also makes it available in the radial menu editor. The binding itself must still be registered by Controlify or another @@ -58,8 +58,7 @@ icon. For `"model": "example:radial/open_overlay"`, Minecraft loads: For example, the item model definition can point at a regular generated model: - -```json !!tabs assets/example/items/radial/open_overlay.json +```json [assets/example/items/radial/open_overlay.json] { "model": { "type": "minecraft:model", @@ -68,7 +67,7 @@ For example, the item model definition can point at a regular generated model: } ``` -```json !!tabs assets/example/models/item/radial/open_overlay.json +```json [assets/example/models/item/radial/open_overlay.json] { "parent": "minecraft:item/generated", "textures": { @@ -76,7 +75,6 @@ For example, the item model definition can point at a regular generated model: } } ``` - The texture used by that example belongs at `assets/example/textures/item/radial/open_overlay.png`. @@ -132,15 +130,13 @@ or translation key, not its translated text. For example: The mod can assign the generated binding an icon by including only this resource: - -```json !!tabs assets/controlify/radial_icons.json +```json [assets/controlify/radial_icons.json] { "controlify_modded:key.example.open_backpack": { "model": "example:backpack" } } ``` - If the mod later registers and correlates an explicit Controlify binding for that key mapping, the automatic binding is not created. Its radial icon entry should then use the explicit binding's ID instead. diff --git a/docs/users/controller-support.mdx b/docs/content/users/controller-support/index.md similarity index 60% rename from docs/users/controller-support.mdx rename to docs/content/users/controller-support/index.md index 1a5e27aed..24bd385a3 100644 --- a/docs/users/controller-support.mdx +++ b/docs/content/users/controller-support/index.md @@ -2,6 +2,8 @@ title: Controller Support Status --- +# Controller Support Status + Controlify uses [SDL3](https://libsdl.org/) to communicate with most controllers, so a controller supported by SDL3 will usually provide its standard buttons, sticks, and triggers to Controlify. This is not a guarantee that every controller or every feature exposed by SDL3 will work. Support can also change with the controller's mode, connection @@ -35,26 +37,7 @@ does not currently use. - ❌ **Unsupported**: this combination does not make the feature available to Controlify. - β€” **Not applicable**: the feature is not relevant to this row. -| Controller | Connection | Operating system | Standard input | Rumble | Extra inputs | Gyro | Touchpad | HD haptics | Adaptive triggers | -|------------|------------|------------------|----------------|--------|--------------|------|----------|------------|-------------------| -| XInput-compatible controller | β€” | Windows | βœ… | βœ… | β€” | β€” | β€” | β€” | β€” | -| Xbox Elite Series 2 | USB | Windows | βœ… | βœ… | ❌ | β€” | β€” | β€” | β€” | -| Xbox Elite Series 2 | USB | Linux | βœ… | βœ… | βœ… | β€” | β€” | β€” | β€” | -| Xbox Elite Series 2 | USB | macOS | βœ… | βœ… | βœ… | β€” | β€” | β€” | β€” | -| Sony DualSense | USB | Windows | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | -| Sony DualSense | Bluetooth | Windows | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… | -| Sony DualSense | USB | Linux | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… | βœ… | -| Sony DualSense | Bluetooth | Linux | βœ… | βœ… | βœ… | βœ… | ❌ | ❌ | βœ… | -| Sony DualSense | USB | macOS | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… | -| Sony DualSense | Bluetooth | macOS | βœ… | βœ… | βœ… | βœ… | βœ… | ❌ | βœ… | -| [Steam Deck](./controller-setup-guides/steam-deck) | Built in | SteamOS | βœ… | βœ… | ❌ | ❌ | ❌ | β€” | β€” | -| [Steam Deck](./controller-setup-guides/steam-deck) | Built in | Windows | βœ… | βœ… | βœ… | βœ… | βœ… | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | USB | Windows | βœ… | βœ… | βœ… | β€” | β€” | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | USB | Linux | βœ… | βœ… | βœ… | β€” | β€” | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | USB | macOS | βœ… | βœ… | βœ… | β€” | β€” | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | Bluetooth | Windows | πŸ› οΈ | ❌ | πŸ› οΈ | β€” | β€” | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | Bluetooth | Linux | πŸ› οΈ | πŸ› οΈ | πŸ› οΈ | β€” | β€” | β€” | β€” | -| [Google Stadia](./controller-setup-guides/stadia-controller) | Bluetooth | macOS | πŸ› οΈ | πŸ› οΈ | πŸ› οΈ | β€” | β€” | β€” | β€” | + ## A controller is missing diff --git a/docs/content/users/controller-support/playstation-controllers.md b/docs/content/users/controller-support/playstation-controllers.md new file mode 100644 index 000000000..bc16161d7 --- /dev/null +++ b/docs/content/users/controller-support/playstation-controllers.md @@ -0,0 +1,42 @@ +--- +title: Playstation Controllers +--- + +# Playstation Controllers + + + +## Fixing touchpad on Linux + +On modern Linux systems, there exists a driver, `hid-playstation`, that detects the touchpad as a system touchpad. +If you want to make use of the touchpad in Controlify (currently only using the touchpad as a button), then you will +need to create a `udev` rule to prevent Linux from recognizing the touchpad. + +Create the following udev rule: + +```bash +sudo nano /etc/udev/rules.d/99-controlify-playstation-touchpad.rules +``` + +It may ask for your password, then add: + +``` +ACTION!="remove", KERNEL=="event[0-9]*", \ + ENV{ID_VENDOR_ID}=="054c", \ + ENV{ID_INPUT_TOUCHPAD}=="1", \ + ENV{LIBINPUT_IGNORE_DEVICE}="1" +``` + +Save the file (Ctrl+O, then Enter), then reload the rules: + +```bash +sudo udevadm control --reload-rules +sudo udevadm trigger +``` + +Disconnect and reconnect your controller after running the commands. + +This prevents any USB/Bluetooth device identifying itself as made by Sony (vendor ID `0x54c`) from being +recognized as a touchpad. + +If you want to reverse these changes, delete the file and re-run the commands. diff --git a/docs/users/controller-setup-guides/stadia-controller.mdx b/docs/content/users/controller-support/stadia-controller.md similarity index 86% rename from docs/users/controller-setup-guides/stadia-controller.mdx rename to docs/content/users/controller-support/stadia-controller.md index c10bd4100..9c56b4f4a 100644 --- a/docs/users/controller-setup-guides/stadia-controller.mdx +++ b/docs/content/users/controller-support/stadia-controller.md @@ -2,6 +2,10 @@ title: Stadia Controller --- +# Stadia Controller + + + In order to use your Stadia controller with Bluetooth, you must patch the firmware to give it Bluetooth support. Stadia controllers were shipped with only Wi-Fi support, for the now dead Stadia streaming platform. diff --git a/docs/users/controller-setup-guides/steam-deck.mdx b/docs/content/users/controller-support/valve-hardware.md similarity index 75% rename from docs/users/controller-setup-guides/steam-deck.mdx rename to docs/content/users/controller-support/valve-hardware.md index 6cc5f2c98..74b05dcf1 100644 --- a/docs/users/controller-setup-guides/steam-deck.mdx +++ b/docs/content/users/controller-support/valve-hardware.md @@ -1,19 +1,25 @@ --- -title: Steam Deck +title: Valve Hardware --- +# Valve Hardware + +## Steam Deck + + + Controlify supports standard controller input and rumble on both LCD and OLED Steam Deck models. - - A SteamOS update broke the integration used by Controlify's enhanced Steam Deck driver, so the driver is disabled - in the latest Controlify release. Installing Decky Loader or changing Controlify's enhanced-driver setting will - not restore it while it is disabled. Standard controller input and rumble continue to work. - +::: danger Enhanced driver temporarily unavailable +A SteamOS update broke the integration used by Controlify's enhanced Steam Deck driver, so the driver is disabled +in the latest Controlify release. Installing Decky Loader or changing Controlify's enhanced-driver setting will +not restore it while it is disabled. Standard controller input and rumble continue to work. +::: The enhanced driver provides the four back buttons, gyro, touchpad clicks, automatic game pausing, and Steam screenshot integration. These features are unavailable on SteamOS until the driver can be enabled again. -## Set up the enhanced driver +### Set up the enhanced driver The following setup applies when enhanced driver support is available: @@ -33,23 +39,27 @@ connection securely, so no Controlify-specific Decky plugin is required. Once connected, the back buttons and touchpad clicks appear as bindable controller buttons, and Controlify can use the Deck's gyro for motion aiming. Touch position, movement, and gestures are not currently used by Controlify. -## Troubleshooting +### Troubleshooting These steps apply after enhanced driver support has been restored. They cannot work around the current SteamOS incompatibility. -### Decky Required +#### Decky Required Controlify could not connect to SteamOS's browser debugger. Confirm that Decky Loader is installed and running, then restart Minecraft from Gaming Mode. You do not need to install a plugin from the Decky store for Controlify. -### Desktop Mode Detected +#### Desktop Mode Detected Return to Gaming Mode before launching Minecraft. Desktop Mode's keyboard-and-mouse conversion prevents Controlify from using the Steam Deck as its native built-in controller. -### Enhanced Steam Deck driver not running +#### Enhanced Steam Deck driver not running Check that **Use Enhanced Steam Deck Driver** remains enabled in Controlify's global settings, that Decky Loader is available in the Steam Deck Quick Access menu, and that Minecraft was launched after Decky Loader started. Restart Minecraft after correcting any of these conditions. + +## Steam Controller (2026) + + diff --git a/docs/users/mod-comparison.mdx b/docs/content/users/mod-comparison.md similarity index 99% rename from docs/users/mod-comparison.mdx rename to docs/content/users/mod-comparison.md index da0f89e1a..a29d9d3e5 100644 --- a/docs/users/mod-comparison.mdx +++ b/docs/content/users/mod-comparison.md @@ -2,6 +2,8 @@ title: Mod Comparison --- +# Mod Comparison + **A table such as this does not reflect the general user experience of each mod such as the user interface and the quality of the mentioned features, and you should try them all out before making a decision. This table is also not exhaustive, and there are many more features that are not listed here.** diff --git a/docs/developers/_meta.json b/docs/developers/_meta.json deleted file mode 100644 index a116b8746..000000000 --- a/docs/developers/_meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "getting-started.mdx": "Getting Started", - "controlify-entrypoint.mdx": "Controlify Entrypoint", - "bindings-api.mdx": "Bindings API", - "screen-operation-api.mdx": "Screen Operation API", - "guide-api.mdx": "Guides API", - "adaptive-trigger-api.mdx": "Adaptive Trigger API" -} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..450e2addb --- /dev/null +++ b/docs/package.json @@ -0,0 +1,21 @@ +{ + "name": "docs", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest", + "vitepress": "^2.0.0-alpha.19", + "vue": "^3.5.41", + "wrangler": "^4.120.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview", + "deploy": "wrangler deploy" + } +} diff --git a/docs/public/controlify-banner.png b/docs/public/controlify-banner.png new file mode 100644 index 000000000..943bc98f8 Binary files /dev/null and b/docs/public/controlify-banner.png differ diff --git a/docs/public/controlify-icon.png b/docs/public/controlify-icon.png new file mode 100644 index 000000000..171157c5c Binary files /dev/null and b/docs/public/controlify-icon.png differ diff --git a/docs/public/icon-256-bg.png b/docs/public/icon-256-bg.png new file mode 100644 index 000000000..ea0119671 Binary files /dev/null and b/docs/public/icon-256-bg.png differ diff --git a/docs/public/icon-256.png b/docs/public/icon-256.png new file mode 100644 index 000000000..dcb709481 Binary files /dev/null and b/docs/public/icon-256.png differ diff --git a/docs/public/images/button-glyph-atlas.webp b/docs/public/images/button-glyph-atlas.webp new file mode 100644 index 000000000..6f0eca65d Binary files /dev/null and b/docs/public/images/button-glyph-atlas.webp differ diff --git a/docs/public/images/chat-with-keyboard.webp b/docs/public/images/chat-with-keyboard.webp new file mode 100644 index 000000000..3a8d04e22 Binary files /dev/null and b/docs/public/images/chat-with-keyboard.webp differ diff --git a/docs/public/images/container-guide.webp b/docs/public/images/container-guide.webp new file mode 100644 index 000000000..dd0342a86 Binary files /dev/null and b/docs/public/images/container-guide.webp differ diff --git a/docs/public/images/controls-screen.png b/docs/public/images/controls-screen.png new file mode 100644 index 000000000..e88296d60 Binary files /dev/null and b/docs/public/images/controls-screen.png differ diff --git a/docs/public/images/controls-screen.webp b/docs/public/images/controls-screen.webp new file mode 100644 index 000000000..346f22969 Binary files /dev/null and b/docs/public/images/controls-screen.webp differ diff --git a/docs/public/images/hole-in-the-wall.webp b/docs/public/images/hole-in-the-wall.webp new file mode 100644 index 000000000..f8fec5a78 Binary files /dev/null and b/docs/public/images/hole-in-the-wall.webp differ diff --git a/docs/public/images/hypixel-pit-bow.webp b/docs/public/images/hypixel-pit-bow.webp new file mode 100644 index 000000000..8f6082d75 Binary files /dev/null and b/docs/public/images/hypixel-pit-bow.webp differ diff --git a/docs/public/images/hypixel-pit-fighting.webp b/docs/public/images/hypixel-pit-fighting.webp new file mode 100644 index 000000000..4deec3ac4 Binary files /dev/null and b/docs/public/images/hypixel-pit-fighting.webp differ diff --git a/docs/public/images/in-game-guide.webp b/docs/public/images/in-game-guide.webp new file mode 100644 index 000000000..b5e2c7db8 Binary files /dev/null and b/docs/public/images/in-game-guide.webp differ diff --git a/docs/public/images/journeymap-fullscreen.webp b/docs/public/images/journeymap-fullscreen.webp new file mode 100644 index 000000000..b8d00307a Binary files /dev/null and b/docs/public/images/journeymap-fullscreen.webp differ diff --git a/docs/public/images/minimega-glide.webp b/docs/public/images/minimega-glide.webp new file mode 100644 index 000000000..282fd6741 Binary files /dev/null and b/docs/public/images/minimega-glide.webp differ diff --git a/docs/public/images/minimega-lobby.webp b/docs/public/images/minimega-lobby.webp new file mode 100644 index 000000000..639143304 Binary files /dev/null and b/docs/public/images/minimega-lobby.webp differ diff --git a/docs/public/images/multiplayer-gameplay.webp b/docs/public/images/multiplayer-gameplay.webp new file mode 100644 index 000000000..11e86896a Binary files /dev/null and b/docs/public/images/multiplayer-gameplay.webp differ diff --git a/docs/public/images/options-button-with-glyph.webp b/docs/public/images/options-button-with-glyph.webp new file mode 100644 index 000000000..5930098a2 Binary files /dev/null and b/docs/public/images/options-button-with-glyph.webp differ diff --git a/docs/public/images/radial-menu-edit.webp b/docs/public/images/radial-menu-edit.webp new file mode 100644 index 000000000..42635f616 Binary files /dev/null and b/docs/public/images/radial-menu-edit.webp differ diff --git a/docs/public/images/radial-menu.webp b/docs/public/images/radial-menu.webp new file mode 100644 index 000000000..c3c0bb5f1 Binary files /dev/null and b/docs/public/images/radial-menu.webp differ diff --git a/docs/public/images/sodium-options.webp b/docs/public/images/sodium-options.webp new file mode 100644 index 000000000..79573f611 Binary files /dev/null and b/docs/public/images/sodium-options.webp differ diff --git a/docs/schemas/keyboard_layout.json b/docs/public/schemas/keyboard_layout.json similarity index 98% rename from docs/schemas/keyboard_layout.json rename to docs/public/schemas/keyboard_layout.json index 61e4b1505..440d69049 100644 --- a/docs/schemas/keyboard_layout.json +++ b/docs/public/schemas/keyboard_layout.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://controlify.dev/schemas/keyboard-layout.json", + "$id": "https://controlify.isxander.dev/schemas/keyboard-layout.json", "title": "Controlify Keyboard Layout", "type": "object", "additionalProperties": false, diff --git a/docs/reference/_meta.json b/docs/reference/_meta.json deleted file mode 100644 index fcee6fc8e..000000000 --- a/docs/reference/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "builtin-bindings.mdx": "Built-in bindings", - "builtin-inputs.mdx": "Built-in inputs", - "builtin-controller-namespaces.mdx": "Built-in controller namespaces" -} diff --git a/docs/resource-packs/_meta.json b/docs/resource-packs/_meta.json deleted file mode 100644 index ce3a6421d..000000000 --- a/docs/resource-packs/_meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "custom-controller-identification.mdx": "Custom Controller Identification", - "default-binds.mdx": "Default Binds", - "input-glyphs.mdx": "Input Glyphs", - "guides.mdx": "Button Guides", - "keyboard-layouts.mdx": "Keyboard Layouts", - "radial-icons.mdx": "Radial Menu Icons", - "adaptive-trigger-effects.mdx": "Adaptive Trigger Effects" -} diff --git a/docs/sinytra-wiki.json b/docs/sinytra-wiki.json deleted file mode 100644 index 41dc38b57..000000000 --- a/docs/sinytra-wiki.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "controlify", - "platforms": { - "modrinth": "controlify", - "curseforge": "controlify" - } -} diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 000000000..c9eab41f8 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "strict": true, + "skipLibCheck": true, + "types": ["bun"] + }, + "include": [ + ".vitepress/**/*.ts", + ".vitepress/**/*.json" + ] +} diff --git a/docs/users/_meta.json b/docs/users/_meta.json deleted file mode 100644 index 718996a73..000000000 --- a/docs/users/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "controller-support.mdx": "Controller Support", - "controller-setup-guides": "Controller Setup Guides", - "mod-comparison.mdx": "Competing Mod Comparison" -} diff --git a/docs/users/controller-setup-guides/_meta.json b/docs/users/controller-setup-guides/_meta.json deleted file mode 100644 index f1ac8c485..000000000 --- a/docs/users/controller-setup-guides/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "steam-deck.mdx": "Steam Deck", - "steam-controller.mdx": "Steam Controller (2026)", - "stadia-controller.mdx": "Stadia Controller" -} diff --git a/docs/users/controller-setup-guides/steam-controller.mdx b/docs/users/controller-setup-guides/steam-controller.mdx deleted file mode 100644 index c2901554e..000000000 --- a/docs/users/controller-setup-guides/steam-controller.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Steam Controller ---- diff --git a/docs/wrangler.jsonc b/docs/wrangler.jsonc new file mode 100644 index 000000000..216e3165a --- /dev/null +++ b/docs/wrangler.jsonc @@ -0,0 +1,34 @@ +/** + * For more details on how to configure Wrangler, refer to: + * https://developers.cloudflare.com/workers/wrangler/configuration/ + */ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "controlify", + "compatibility_date": "2026-08-11", + + "build": { + "command": "bun run build" + }, + "assets": { + "directory": "./.vitepress/dist" + }, + + // CDN Cache for Workers! + "cache": { + "enabled": true + }, + + // Do not publish site to *.workers.dev + "workers_dev": false, + // ...except for preview deployments + "preview_urls": true, + + "routes": [ + { + "pattern": "controlify.isxander.dev", + "zone_name": "isxander.dev", + "custom_domain": true + } + ] +} diff --git a/gradle.properties b/gradle.properties index 84089f5ff..8887d0aeb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,9 @@ mtk.multiloader.createDefaultRuns=false # This build is configuration cache compatible!* # *other than a few upstream bugs... org.gradle.configuration-cache=true +org.gradle.configureondemand=true +org.gradle.parallel=true +org.gradle.caching=true # Increase the heap size for decompilations org.gradle.jvmargs=-Xmx4G --enable-native-access=ALL-UNNAMED diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index 63cacf01f..d13e6863b 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -54,23 +54,24 @@ val printVersion = tasks.register("printVersion") { val modVersion = providers.gradleProperty("mod.version") val publishTargets = stonecutter.versions.joinToString(separator = "\n") { "- ${it.project}" } +val changelogContents = providers.fileContents(layout.projectDirectory.file("CHANGELOG.md")) + .asText + .zip(modVersion) { changelog, version -> + changelog.replace("{version}", version) + } + .zip(provider { publishTargets }) { changelog, targets -> + changelog.replace( + "{targets}", + targets + ) + } publishMods { dryRun = false version = modVersion - changelog = providers.fileContents(layout.projectDirectory.file("CHANGELOG.md")) - .asText - .zip(modVersion) { changelog, version -> - changelog.replace("{version}", version) - } - .map { changelog -> - changelog.replace( - "{targets}", - publishTargets - ) - } + changelog = changelogContents type = modVersion.map { version -> when { @@ -107,6 +108,21 @@ publishMods { } } +tasks.register("writeChangelogToDocs") { + group = "controlify/internal" + description = "Copies the templated CHANGELOG.md into /docs changelog archive" + + val outputFile = layout.projectDirectory.file("docs/content/changelog/${version}.md") + + inputs.property("contents", changelogContents) + outputs.file(outputFile) + + doLast { + val contents = inputs.properties["contents"] as String + outputFile.asFile.writeText(contents) + } +} + spotless { java { target("src/**/*.java")