From 23621d70468d5c7d99bd6de770177fb6aa87e059 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 12 Oct 2025 22:12:23 -0500 Subject: [PATCH] Update source files --- .cargo/config.toml | 11 + .github/workflows/builds.yml | 177 + .github/workflows/quality.yml | 84 + .gitignore | 33 + .source-commit | 1 + Cargo.lock | 7806 +++++++++++++++++ Cargo.toml | 47 + Justfile | 92 + LICENSE | 13 + crates/borders-core/Cargo.toml | 89 + crates/borders-core/assets/maps/World.json | 76 + crates/borders-core/assets/maps/World.png | Bin 0 -> 189026 bytes crates/borders-core/build.rs | 28 + crates/borders-core/src/app.rs | 140 + crates/borders-core/src/build_info.rs | 21 + crates/borders-core/src/constants.rs | 8 + crates/borders-core/src/game/action.rs | 40 + crates/borders-core/src/game/borders.rs | 211 + crates/borders-core/src/game/bot.rs | 521 ++ crates/borders-core/src/game/combat/active.rs | 256 + .../src/game/combat/calculator.rs | 152 + .../borders-core/src/game/combat/executor.rs | 333 + crates/borders-core/src/game/combat/mod.rs | 7 + crates/borders-core/src/game/connectivity.rs | 155 + crates/borders-core/src/game/constants.rs | 49 + crates/borders-core/src/game/game_instance.rs | 420 + crates/borders-core/src/game/income.rs | 52 + .../borders-core/src/game/input_handlers.rs | 266 + crates/borders-core/src/game/lifecycle.rs | 171 + crates/borders-core/src/game/local_context.rs | 71 + crates/borders-core/src/game/mod.rs | 51 + crates/borders-core/src/game/outcome.rs | 82 + crates/borders-core/src/game/player.rs | 199 + .../borders-core/src/game/player_manager.rs | 72 + crates/borders-core/src/game/rng.rs | 121 + crates/borders-core/src/game/ships/mod.rs | 12 + .../src/game/ships/ship_manager.rs | 158 + .../src/game/ships/ship_pathfinding.rs | 310 + .../src/game/ships/transport_ship.rs | 88 + crates/borders-core/src/game/spawn_manager.rs | 67 + crates/borders-core/src/game/spawn_timeout.rs | 74 + crates/borders-core/src/game/terrain.rs | 270 + crates/borders-core/src/game/territory.rs | 21 + .../src/game/territory_manager.rs | 202 + .../borders-core/src/game/tile_ownership.rs | 159 + crates/borders-core/src/game/tilemap.rs | 441 + .../borders-core/src/game/tilemap_changes.rs | 179 + crates/borders-core/src/game/turn.rs | 34 + crates/borders-core/src/game/utils.rs | 66 + crates/borders-core/src/lib.rs | 18 + crates/borders-core/src/networking/client.rs | 103 + .../src/networking/coordinator.rs | 189 + crates/borders-core/src/networking/dns.rs | 98 + crates/borders-core/src/networking/mod.rs | 119 + crates/borders-core/src/networking/network.rs | 244 + .../borders-core/src/networking/protocol.rs | 23 + .../src/networking/server/broadcast.rs | 58 + .../networking/server/connection_handler.rs | 208 + .../borders-core/src/networking/server/mod.rs | 12 + .../src/networking/server/registry.rs | 72 + crates/borders-core/src/platform.rs | 59 + crates/borders-core/src/plugin.rs | 739 ++ crates/borders-core/src/telemetry/client.rs | 311 + crates/borders-core/src/telemetry/mod.rs | 128 + .../borders-core/src/telemetry/system_info.rs | 148 + crates/borders-core/src/telemetry/types.rs | 77 + crates/borders-core/src/telemetry/user_id.rs | 268 + crates/borders-core/src/time.rs | 58 + crates/borders-core/src/ui/input.rs | 350 + crates/borders-core/src/ui/leaderboard.rs | 160 + crates/borders-core/src/ui/mod.rs | 104 + crates/borders-core/src/ui/plugin.rs | 50 + crates/borders-core/src/ui/protocol.rs | 518 ++ crates/borders-core/src/ui/transport.rs | 236 + crates/borders-core/tests/terrain.rs | 50 + crates/borders-desktop/.gitignore | 7 + crates/borders-desktop/Cargo.toml | 29 + crates/borders-desktop/build.rs | 3 + .../borders-desktop/capabilities/default.json | 17 + crates/borders-desktop/icons/128x128.png | Bin 0 -> 3512 bytes crates/borders-desktop/icons/128x128@2x.png | Bin 0 -> 7012 bytes crates/borders-desktop/icons/32x32.png | Bin 0 -> 974 bytes .../icons/Square107x107Logo.png | Bin 0 -> 2863 bytes .../icons/Square142x142Logo.png | Bin 0 -> 3858 bytes .../icons/Square150x150Logo.png | Bin 0 -> 3966 bytes .../icons/Square284x284Logo.png | Bin 0 -> 7737 bytes .../borders-desktop/icons/Square30x30Logo.png | Bin 0 -> 903 bytes .../icons/Square310x310Logo.png | Bin 0 -> 8591 bytes .../borders-desktop/icons/Square44x44Logo.png | Bin 0 -> 1299 bytes .../borders-desktop/icons/Square71x71Logo.png | Bin 0 -> 2011 bytes .../borders-desktop/icons/Square89x89Logo.png | Bin 0 -> 2468 bytes crates/borders-desktop/icons/StoreLogo.png | Bin 0 -> 1523 bytes crates/borders-desktop/icons/icon.icns | Bin 0 -> 98451 bytes crates/borders-desktop/icons/icon.ico | Bin 0 -> 86642 bytes crates/borders-desktop/icons/icon.png | Bin 0 -> 14183 bytes crates/borders-desktop/src/analytics.rs | 51 + crates/borders-desktop/src/main.rs | 81 + crates/borders-desktop/src/plugin.rs | 176 + crates/borders-desktop/src/render_bridge.rs | 162 + crates/borders-desktop/tauri.conf.json | 40 + crates/borders-server/Cargo.toml | 14 + crates/borders-server/src/main.rs | 145 + crates/borders-wasm/Cargo.toml | 33 + crates/borders-wasm/src/bridge.rs | 67 + crates/borders-wasm/src/lib.rs | 98 + crates/borders-wasm/src/render_bridge.rs | 68 + frontend/.prettierrc | 5 + frontend/index.html | 7 + frontend/package.json | 48 + frontend/pages/+Head.tsx | 73 + frontend/pages/+Wrapper.client.tsx | 70 + frontend/pages/+Wrapper.tsx | 17 + frontend/pages/+config.js | 14 + frontend/pages/fonts.css | 19 + frontend/pages/index/+Page.css | 543 ++ frontend/pages/index/+Page.tsx | 71 + frontend/pages/index/GameContainer.client.tsx | 170 + frontend/pnpm-lock.yaml | 2487 ++++++ frontend/pnpm-workspace.yaml | 3 + frontend/src/assets/multiplayer.png | Bin 0 -> 3117 bytes frontend/src/assets/multiplayer.psd | Bin 0 -> 48700 bytes frontend/src/assets/screenshot.png | Bin 0 -> 184950 bytes frontend/src/assets/settings.png | Bin 0 -> 2520 bytes frontend/src/assets/settings.psd | Bin 0 -> 48884 bytes frontend/src/assets/singleplayer.png | Bin 0 -> 1519 bytes frontend/src/assets/singleplayer.psd | Bin 0 -> 29550 bytes frontend/src/browser/analytics/index.ts | 5 + .../src/browser/analytics/wasmAnalytics.ts | 30 + frontend/src/browser/api/wasmBridge.ts | 344 + frontend/src/browser/workers/game.worker.ts | 84 + .../src/desktop/analytics/tauriAnalytics.ts | 32 + frontend/src/desktop/api/tauriAPI.ts | 421 + frontend/src/shared/analytics/AnalyticsAPI.ts | 24 + .../src/shared/analytics/AnalyticsContext.tsx | 13 + frontend/src/shared/analytics/index.ts | 2 + frontend/src/shared/api/GameAPI.ts | 100 + frontend/src/shared/api/GameAPIContext.tsx | 29 + frontend/src/shared/api/index.ts | 4 + frontend/src/shared/api/types.ts | 72 + .../shared/components/AlphaWarningModal.css | 164 + .../shared/components/AlphaWarningModal.tsx | 43 + .../src/shared/components/AttackControls.tsx | 157 + frontend/src/shared/components/AttackRow.tsx | 73 + frontend/src/shared/components/Attacks.tsx | 74 + frontend/src/shared/components/GameCanvas.tsx | 228 + .../src/shared/components/GameEndOverlay.tsx | 133 + frontend/src/shared/components/GameMenu.css | 176 + frontend/src/shared/components/GameMenu.tsx | 75 + .../src/shared/components/Leaderboard.tsx | 226 + frontend/src/shared/components/MenuScreen.css | 469 + frontend/src/shared/components/MenuScreen.tsx | 119 + .../shared/components/SpawnPhaseOverlay.css | 67 + .../shared/components/SpawnPhaseOverlay.tsx | 57 + frontend/src/shared/hooks/index.ts | 1 + .../src/shared/hooks/useThrottledCallback.ts | 75 + .../src/shared/render/CameraController.ts | 377 + frontend/src/shared/render/GameRenderer.ts | 477 + frontend/src/shared/render/ShipLayer.ts | 201 + frontend/src/shared/render/TerritoryLayer.ts | 412 + frontend/src/shared/render/index.ts | 2 + frontend/src/shared/utils/binaryDecoding.ts | 59 + frontend/src/vite-env.d.ts | 17 + frontend/tsconfig.browser.json | 4 + frontend/tsconfig.desktop.json | 4 + frontend/tsconfig.json | 32 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 127 + rust-toolchain.toml | 3 + scripts/release-manager/.env.example | 19 + scripts/release-manager/.gitignore | 16 + scripts/release-manager/bun.lock | 741 ++ scripts/release-manager/cli.ts | 605 ++ scripts/release-manager/manifest.json | 132 + scripts/release-manager/package.json | 26 + scripts/release-manager/pnpm-workspace.yaml | 2 + scripts/release-manager/tsconfig.json | 19 + scripts/release-manager/types.ts | 54 + scripts/release-manager/utils.ts | 162 + 178 files changed, 29652 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/builds.yml create mode 100644 .github/workflows/quality.yml create mode 100644 .gitignore create mode 100644 .source-commit create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Justfile create mode 100644 LICENSE create mode 100644 crates/borders-core/Cargo.toml create mode 100644 crates/borders-core/assets/maps/World.json create mode 100644 crates/borders-core/assets/maps/World.png create mode 100644 crates/borders-core/build.rs create mode 100644 crates/borders-core/src/app.rs create mode 100644 crates/borders-core/src/build_info.rs create mode 100644 crates/borders-core/src/constants.rs create mode 100644 crates/borders-core/src/game/action.rs create mode 100644 crates/borders-core/src/game/borders.rs create mode 100644 crates/borders-core/src/game/bot.rs create mode 100644 crates/borders-core/src/game/combat/active.rs create mode 100644 crates/borders-core/src/game/combat/calculator.rs create mode 100644 crates/borders-core/src/game/combat/executor.rs create mode 100644 crates/borders-core/src/game/combat/mod.rs create mode 100644 crates/borders-core/src/game/connectivity.rs create mode 100644 crates/borders-core/src/game/constants.rs create mode 100644 crates/borders-core/src/game/game_instance.rs create mode 100644 crates/borders-core/src/game/income.rs create mode 100644 crates/borders-core/src/game/input_handlers.rs create mode 100644 crates/borders-core/src/game/lifecycle.rs create mode 100644 crates/borders-core/src/game/local_context.rs create mode 100644 crates/borders-core/src/game/mod.rs create mode 100644 crates/borders-core/src/game/outcome.rs create mode 100644 crates/borders-core/src/game/player.rs create mode 100644 crates/borders-core/src/game/player_manager.rs create mode 100644 crates/borders-core/src/game/rng.rs create mode 100644 crates/borders-core/src/game/ships/mod.rs create mode 100644 crates/borders-core/src/game/ships/ship_manager.rs create mode 100644 crates/borders-core/src/game/ships/ship_pathfinding.rs create mode 100644 crates/borders-core/src/game/ships/transport_ship.rs create mode 100644 crates/borders-core/src/game/spawn_manager.rs create mode 100644 crates/borders-core/src/game/spawn_timeout.rs create mode 100644 crates/borders-core/src/game/terrain.rs create mode 100644 crates/borders-core/src/game/territory.rs create mode 100644 crates/borders-core/src/game/territory_manager.rs create mode 100644 crates/borders-core/src/game/tile_ownership.rs create mode 100644 crates/borders-core/src/game/tilemap.rs create mode 100644 crates/borders-core/src/game/tilemap_changes.rs create mode 100644 crates/borders-core/src/game/turn.rs create mode 100644 crates/borders-core/src/game/utils.rs create mode 100644 crates/borders-core/src/lib.rs create mode 100644 crates/borders-core/src/networking/client.rs create mode 100644 crates/borders-core/src/networking/coordinator.rs create mode 100644 crates/borders-core/src/networking/dns.rs create mode 100644 crates/borders-core/src/networking/mod.rs create mode 100644 crates/borders-core/src/networking/network.rs create mode 100644 crates/borders-core/src/networking/protocol.rs create mode 100644 crates/borders-core/src/networking/server/broadcast.rs create mode 100644 crates/borders-core/src/networking/server/connection_handler.rs create mode 100644 crates/borders-core/src/networking/server/mod.rs create mode 100644 crates/borders-core/src/networking/server/registry.rs create mode 100644 crates/borders-core/src/platform.rs create mode 100644 crates/borders-core/src/plugin.rs create mode 100644 crates/borders-core/src/telemetry/client.rs create mode 100644 crates/borders-core/src/telemetry/mod.rs create mode 100644 crates/borders-core/src/telemetry/system_info.rs create mode 100644 crates/borders-core/src/telemetry/types.rs create mode 100644 crates/borders-core/src/telemetry/user_id.rs create mode 100644 crates/borders-core/src/time.rs create mode 100644 crates/borders-core/src/ui/input.rs create mode 100644 crates/borders-core/src/ui/leaderboard.rs create mode 100644 crates/borders-core/src/ui/mod.rs create mode 100644 crates/borders-core/src/ui/plugin.rs create mode 100644 crates/borders-core/src/ui/protocol.rs create mode 100644 crates/borders-core/src/ui/transport.rs create mode 100644 crates/borders-core/tests/terrain.rs create mode 100644 crates/borders-desktop/.gitignore create mode 100644 crates/borders-desktop/Cargo.toml create mode 100644 crates/borders-desktop/build.rs create mode 100644 crates/borders-desktop/capabilities/default.json create mode 100644 crates/borders-desktop/icons/128x128.png create mode 100644 crates/borders-desktop/icons/128x128@2x.png create mode 100644 crates/borders-desktop/icons/32x32.png create mode 100644 crates/borders-desktop/icons/Square107x107Logo.png create mode 100644 crates/borders-desktop/icons/Square142x142Logo.png create mode 100644 crates/borders-desktop/icons/Square150x150Logo.png create mode 100644 crates/borders-desktop/icons/Square284x284Logo.png create mode 100644 crates/borders-desktop/icons/Square30x30Logo.png create mode 100644 crates/borders-desktop/icons/Square310x310Logo.png create mode 100644 crates/borders-desktop/icons/Square44x44Logo.png create mode 100644 crates/borders-desktop/icons/Square71x71Logo.png create mode 100644 crates/borders-desktop/icons/Square89x89Logo.png create mode 100644 crates/borders-desktop/icons/StoreLogo.png create mode 100644 crates/borders-desktop/icons/icon.icns create mode 100644 crates/borders-desktop/icons/icon.ico create mode 100644 crates/borders-desktop/icons/icon.png create mode 100644 crates/borders-desktop/src/analytics.rs create mode 100644 crates/borders-desktop/src/main.rs create mode 100644 crates/borders-desktop/src/plugin.rs create mode 100644 crates/borders-desktop/src/render_bridge.rs create mode 100644 crates/borders-desktop/tauri.conf.json create mode 100644 crates/borders-server/Cargo.toml create mode 100644 crates/borders-server/src/main.rs create mode 100644 crates/borders-wasm/Cargo.toml create mode 100644 crates/borders-wasm/src/bridge.rs create mode 100644 crates/borders-wasm/src/lib.rs create mode 100644 crates/borders-wasm/src/render_bridge.rs create mode 100644 frontend/.prettierrc create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/pages/+Head.tsx create mode 100644 frontend/pages/+Wrapper.client.tsx create mode 100644 frontend/pages/+Wrapper.tsx create mode 100644 frontend/pages/+config.js create mode 100644 frontend/pages/fonts.css create mode 100644 frontend/pages/index/+Page.css create mode 100644 frontend/pages/index/+Page.tsx create mode 100644 frontend/pages/index/GameContainer.client.tsx create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/pnpm-workspace.yaml create mode 100644 frontend/src/assets/multiplayer.png create mode 100644 frontend/src/assets/multiplayer.psd create mode 100644 frontend/src/assets/screenshot.png create mode 100644 frontend/src/assets/settings.png create mode 100644 frontend/src/assets/settings.psd create mode 100644 frontend/src/assets/singleplayer.png create mode 100644 frontend/src/assets/singleplayer.psd create mode 100644 frontend/src/browser/analytics/index.ts create mode 100644 frontend/src/browser/analytics/wasmAnalytics.ts create mode 100644 frontend/src/browser/api/wasmBridge.ts create mode 100644 frontend/src/browser/workers/game.worker.ts create mode 100644 frontend/src/desktop/analytics/tauriAnalytics.ts create mode 100644 frontend/src/desktop/api/tauriAPI.ts create mode 100644 frontend/src/shared/analytics/AnalyticsAPI.ts create mode 100644 frontend/src/shared/analytics/AnalyticsContext.tsx create mode 100644 frontend/src/shared/analytics/index.ts create mode 100644 frontend/src/shared/api/GameAPI.ts create mode 100644 frontend/src/shared/api/GameAPIContext.tsx create mode 100644 frontend/src/shared/api/index.ts create mode 100644 frontend/src/shared/api/types.ts create mode 100644 frontend/src/shared/components/AlphaWarningModal.css create mode 100644 frontend/src/shared/components/AlphaWarningModal.tsx create mode 100644 frontend/src/shared/components/AttackControls.tsx create mode 100644 frontend/src/shared/components/AttackRow.tsx create mode 100644 frontend/src/shared/components/Attacks.tsx create mode 100644 frontend/src/shared/components/GameCanvas.tsx create mode 100644 frontend/src/shared/components/GameEndOverlay.tsx create mode 100644 frontend/src/shared/components/GameMenu.css create mode 100644 frontend/src/shared/components/GameMenu.tsx create mode 100644 frontend/src/shared/components/Leaderboard.tsx create mode 100644 frontend/src/shared/components/MenuScreen.css create mode 100644 frontend/src/shared/components/MenuScreen.tsx create mode 100644 frontend/src/shared/components/SpawnPhaseOverlay.css create mode 100644 frontend/src/shared/components/SpawnPhaseOverlay.tsx create mode 100644 frontend/src/shared/hooks/index.ts create mode 100644 frontend/src/shared/hooks/useThrottledCallback.ts create mode 100644 frontend/src/shared/render/CameraController.ts create mode 100644 frontend/src/shared/render/GameRenderer.ts create mode 100644 frontend/src/shared/render/ShipLayer.ts create mode 100644 frontend/src/shared/render/TerritoryLayer.ts create mode 100644 frontend/src/shared/render/index.ts create mode 100644 frontend/src/shared/utils/binaryDecoding.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.browser.json create mode 100644 frontend/tsconfig.desktop.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 rust-toolchain.toml create mode 100644 scripts/release-manager/.env.example create mode 100644 scripts/release-manager/.gitignore create mode 100644 scripts/release-manager/bun.lock create mode 100644 scripts/release-manager/cli.ts create mode 100644 scripts/release-manager/manifest.json create mode 100644 scripts/release-manager/package.json create mode 100644 scripts/release-manager/pnpm-workspace.yaml create mode 100644 scripts/release-manager/tsconfig.json create mode 100644 scripts/release-manager/types.ts create mode 100644 scripts/release-manager/utils.ts diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..9bd17ea --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"', '--cfg=web_sys_unstable_apis'] + +# for Linux +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +# for Windows +# [target.x86_64-pc-windows-msvc] +# linker = "rust-lld.exe" diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml new file mode 100644 index 0000000..1986317 --- /dev/null +++ b/.github/workflows/builds.yml @@ -0,0 +1,177 @@ +name: Builds +on: + - push +env: + CARGO_TERM_COLOR: always + RUST_VERSION: "stable" +jobs: + desktop: + name: Desktop (${{ matrix.target.os }} / ${{ matrix.target.arch }}) + runs-on: ${{ matrix.target.runner }} + strategy: + fail-fast: false + matrix: + target: + - os: linux + arch: x86_64 + runner: ubuntu-22.04 + rust_target: x86_64-unknown-linux-gnu + - os: macos + arch: x86_64 + runner: macos-15-intel + rust_target: x86_64-apple-darwin + - os: macos + arch: aarch64 + runner: macos-latest + rust_target: aarch64-apple-darwin + - os: windows + arch: x86_64 + runner: windows-latest + rust_target: x86_64-pc-windows-msvc + - os: windows + arch: aarch64 + runner: windows-latest + rust_target: aarch64-pc-windows-msvc + steps: + - uses: actions/checkout@v5 + - name: Setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + components: rustfmt, clippy + targets: ${{ matrix.target.rust_target }} + - name: Rust cache + uses: Swatinem/rust-cache@v2 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile --prefer-offline + working-directory: frontend + - name: Cache Tauri CLI (macOS only) + if: matrix.target.os == 'macos' + id: cache-tauri-cli + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-tauri + key: ${{ runner.os }}-${{ runner.arch }}-tauri-cli-2 + - name: Install cargo-binstall + if: matrix.target.os != 'macos' + uses: taiki-e/install-action@cargo-binstall + - name: Install Tauri CLI (via binstall) + if: matrix.target.os != 'macos' + run: cargo binstall tauri-cli --version '^2' --no-confirm + - name: Install Tauri CLI (from source on macOS) + if: matrix.target.os == 'macos' && steps.cache-tauri-cli.outputs.cache-hit != 'true' + run: cargo install tauri-cli --version '^2' --locked + env: + CARGO_PROFILE_RELEASE_LTO: false + - name: Cache apt packages + if: matrix.target.os == 'linux' + uses: actions/cache@v4 + with: + path: | + /var/cache/apt/archives/*.deb + key: ${{ matrix.target.runner }}-apt-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.target.runner }}-apt- + - name: Install Linux dependencies + if: matrix.target.os == 'linux' + run: | + sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends \ + build-essential \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf + - name: Build desktop app + run: cargo tauri build --target ${{ matrix.target.rust_target }} + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: iron-borders-${{ matrix.target.os }}-${{ matrix.target.arch }} + path: | + target/${{ matrix.target.rust_target }}/release/*.exe + target/${{ matrix.target.rust_target }}/release/bundle/appimage/*.AppImage + target/${{ matrix.target.rust_target }}/release/bundle/deb/*.deb + target/${{ matrix.target.rust_target }}/release/bundle/rpm/*.rpm + target/${{ matrix.target.rust_target }}/release/bundle/dmg/*.dmg + if-no-files-found: ignore + browser: + name: Browser (WASM) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile --prefer-offline + working-directory: frontend + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.104 + - name: Install wasm-opt + uses: taiki-e/install-action@v2 + with: + tool: wasm-opt@0.116.1 + - name: Build WASM release + run: | + cargo build -p borders-wasm --profile wasm-release --target wasm32-unknown-unknown + wasm-bindgen --out-dir pkg --out-name borders --target web target/wasm32-unknown-unknown/wasm-release/borders_wasm.wasm + wasm-opt -Oz --enable-bulk-memory --enable-threads --all-features pkg/borders_bg.wasm -o pkg/borders_bg.wasm + mkdir -p frontend/pkg + cp -r pkg/* frontend/pkg/ + - name: Build frontend (root-based) + run: pnpm run build:browser + working-directory: frontend + - name: Upload browser artifacts + uses: actions/upload-artifact@v4 + with: + name: iron-borders-browser + path: frontend/dist/browser/client/**/* + if-no-files-found: error + deploy-cloudflare: + name: Deploy to Cloudflare Pages + needs: browser + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - name: Download browser artifact + uses: actions/download-artifact@v4 + with: + name: iron-borders-browser + path: dist + - name: Deploy to Cloudflare Pages + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy dist --project-name=borders + - name: Print deployment URL + env: + DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + run: echo "Deployed to $DEPLOYMENT_URL" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..4a98da5 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,84 @@ +name: Quality +on: + - push +env: + CARGO_TERM_COLOR: always + RUST_VERSION: "stable" +jobs: + rust-quality: + name: Rust Quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Cache apt packages + uses: actions/cache@v4 + with: + path: | + /var/cache/apt/archives/*.deb + key: ubuntu-latest-apt-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ubuntu-latest-apt- + - name: Install Linux dependencies + run: | + sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends \ + build-essential \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf + - name: Install just + uses: taiki-e/install-action@just + - name: Install cargo-machete + uses: taiki-e/install-action@cargo-machete + - name: Run Rust checks + run: just --shell bash --shell-arg -c check + - name: Install cargo-audit + uses: taiki-e/install-action@cargo-audit + - name: Run security audit + run: cargo audit + frontend-quality: + name: Frontend Quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.104 + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + working-directory: frontend + - name: Install just + uses: taiki-e/install-action@just + - name: Build WASM for frontend checks + run: | + cargo build -p borders-wasm --profile wasm-dev --target wasm32-unknown-unknown + wasm-bindgen --out-dir pkg --out-name borders --target web target/wasm32-unknown-unknown/wasm-dev/borders_wasm.wasm + mkdir -p frontend/pkg + cp -r pkg/* frontend/pkg/ + - name: Run frontend TypeScript checks + run: pnpm run build:browser + working-directory: frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ce3d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +target/ +pkg/ +*.pem +/*.io/ +releases/ + +# Build-copied shader files +frontend/public/assets/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.source-commit b/.source-commit new file mode 100644 index 0000000..ce07df2 --- /dev/null +++ b/.source-commit @@ -0,0 +1 @@ +a4d0597beda33b4e9da45dc29d66bf6ccff15e37 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..24b1b97 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7806 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libloading 0.8.8", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bevy_ecs" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d929d32190cfcde6efd2df493601c4dbc18a691fd9775a544c951c3c112e1a" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.4", + "bumpalo", + "concurrent-queue", + "derive_more 2.0.1", + "fixedbitset", + "indexmap 2.11.4", + "log", + "nonmax", + "serde", + "slotmap", + "smallvec", + "thiserror 2.0.17", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eeddfb80a2e000663e87be9229c26b4da92bddbc06c8776bc0d1f4a7f679079" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17dbc3f8948da58b3c17767d20fd3cd35fe4721ed19a9a3204a6f1d6c9951bdd" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn 2.0.106", + "toml_edit 0.23.6", +] + +[[package]] +name = "bevy_platform" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cf8cda162688c95250e74cffaa1c3a04597f105d4ca35554106f107308ea57" +dependencies = [ + "critical-section", + "foldhash", + "futures-channel", + "hashbrown 0.16.0", + "js-sys", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin 0.10.0", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_ptr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ab4074e7b781bab84e9b0a41ede245d673d1f75646ce0db27643aedcfb3a85" + +[[package]] +name = "bevy_reflect" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333df3f5947b7e62728eb5c0b51d679716b16c7c5283118fed4563f13230954e" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more 2.0.1", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.17", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0205dce9c5a4d8d041b263bcfd96e9d9d6f3d49416e12db347ab5778b3071fe1" +dependencies = [ + "bevy_macro_utils", + "indexmap 2.11.4", + "proc-macro2", + "quote", + "syn 2.0.106", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18839182775f30d26f0f84d9de85d25361bb593c99517a80b64ede6cbaf41adc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "bevy_platform", + "crossbeam-queue", + "derive_more 2.0.1", + "futures-lite", + "heapless", + "pin-project", +] + +[[package]] +name = "bevy_utils" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080254083c74d5f6eb0649d7cd6181bda277e8fe3c509ec68990a5d56ec23f24" +dependencies = [ + "bevy_platform", + "disqualified", + "thread_local", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borders-core" +version = "0.6.1" +dependencies = [ + "anyhow", + "async-trait", + "bevy_ecs", + "bincode", + "chrono", + "directories", + "flume", + "futures", + "futures-lite", + "glam", + "gloo-timers", + "hex", + "hickory-resolver", + "hmac", + "image", + "js-sys", + "machineid-rs", + "once_cell", + "pem", + "rand 0.9.2", + "reqwest", + "ring", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "slotmap", + "sysinfo 0.33.1", + "tokio", + "tracing", + "url", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "web-transport", + "winreg 0.52.0", +] + +[[package]] +name = "borders-desktop" +version = "0.6.1" +dependencies = [ + "bevy_ecs", + "borders-core", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-process", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-tracy", + "tracy-client", +] + +[[package]] +name = "borders-server" +version = "0.6.1" +dependencies = [ + "anyhow", + "borders-core", + "flume", + "tokio", + "tracing", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "borders-wasm" +version = "0.6.1" +dependencies = [ + "bevy_ecs", + "borders-core", + "console_error_panic_hook", + "getrandom 0.3.3", + "gloo-timers", + "js-sys", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-tracing", + "web-time", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.4", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.7", +] + +[[package]] +name = "cc" +version = "1.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.8", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.106", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "dlopen2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.7", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastbloom" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" +dependencies = [ + "getrandom 0.3.3", + "libm", + "rand 0.9.2", + "siphasher 1.0.1", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glam" +version = "0.30.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +dependencies = [ + "serde_core", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.4", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.11.4", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h3" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfb059a4f28a66f186ed16ad912d142f490676acba59353831d7cb45a96b0d3" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-quinn" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d482318ae94198fc8e3cbb0b7ba3099c865d744e6ec7c62039ca7b6b6c66fbf" +dependencies = [ + "bytes", + "futures", + "h3", + "quinn", + "tokio", + "tokio-util", +] + +[[package]] +name = "half" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "h3", + "h3-quinn", + "http", + "idna", + "ipnet", + "once_cell", + "pin-project-lite", + "quinn", + "rand 0.9.2", + "ring", + "rustls", + "thiserror 2.0.17", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "quinn", + "rand 0.9.2", + "resolv-conf", + "rustls", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tokio-rustls", + "tracing", + "webpki-roots 0.26.11", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.2", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.0", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.4", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.11.4", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.5", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.4", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "machineid-rs" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ceb4d434d69d7199abc3036541ba6ef86767a4356e3077d5a3419f85b70b14" +dependencies = [ + "hex", + "hmac", + "md-5", + "serde", + "serde_json", + "sha-1", + "sha2", + "sysinfo 0.29.11", + "uuid", + "whoami", + "winreg 0.11.0", + "wmi", +] + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "moxcms" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc7d85f3d741164e8972ad355e26ac6e51b20fcae5f911c7da8f2d8bbbb3f33" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.4", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.11.4", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pxfm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.0", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "fastbloom", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.2", +] + +[[package]] +name = "resolv-conf" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.106", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.4", + "schemars 0.9.0", + "schemars 1.0.4", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sysinfo" +version = "0.29.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tao" +version = "0.34.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.3", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.17", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.61.3", +] + +[[package]] +name = "tauri-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.7", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.106", + "tauri-utils", + "thiserror 2.0.17", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.7", + "walkdir", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "url", + "windows 0.61.3", + "zbus", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[package]] +name = "tauri-runtime" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.3", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.17", + "toml 0.9.7", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +dependencies = [ + "embed-resource", + "toml 0.9.7", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap 2.11.4", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.11.4", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.7.13", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.7.2", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow 0.7.13", +] + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-tracy" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracy-client", +] + +[[package]] +name = "tracy-client" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef54005d3d760186fd662dad4b7bb27ecd5531cdef54d1573ebd3f20a9205ed7" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + +[[package]] +name = "tracy-client-sys" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319c70195101a93f56db4c74733e272d720768e13471f400c78406a326b172b0" +dependencies = [ + "cc", + "windows-targets 0.52.6", +] + +[[package]] +name = "tray-icon" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "serde", + "thiserror 2.0.17", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-tracing" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ab253baf6d3772bbdb37a0966b67d37ab80657ccd1a084b4d7b3de3232375d" +dependencies = [ + "tracing", + "tracing-log", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "web-streams" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4d5dbf19463c4b65e974303d453cc11991873c7a4a4953214f791d73303a2" +dependencies = [ + "thiserror 2.0.17", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-transport" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03efb036f13785e3cbb9285e9fb94872a0887a9f4e7efeb02647cc12f5e55eea" +dependencies = [ + "bytes", + "thiserror 2.0.17", + "url", + "web-transport-quinn", + "web-transport-wasm", +] + +[[package]] +name = "web-transport-proto" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb650c577c46254d16041c7fe0dc9901d9a42df3f46e77e9d05d1b3c17294b19" +dependencies = [ + "bytes", + "http", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "web-transport-quinn" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccba8a1ab2aae70119c9ada57e4a2324ebe1820a18814ca47a818f01dcd00f46" +dependencies = [ + "bytes", + "futures", + "http", + "log", + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.17", + "tokio", + "url", + "web-transport-proto", + "web-transport-trait", +] + +[[package]] +name = "web-transport-trait" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850148841799c83f033f4dddccb219f1f097aff6db1bda5b0d3be69fefb32bd" +dependencies = [ + "bytes", +] + +[[package]] +name = "web-transport-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b2af1bed400caa442d75217bb7ece5e1ec853daf60affd5d2448c0a2063662" +dependencies = [ + "bytes", + "js-sys", + "thiserror 2.0.17", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-streams", + "web-sys", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu-types" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "js-sys", + "log", + "serde", + "thiserror 2.0.17", + "web-sys", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement 0.48.0", + "windows-interface 0.48.0", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wmi" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daffb44abb7d2e87a1233aa17fdbde0d55b890b32a23a1f908895b87fa6f1a00" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 1.0.69", + "windows 0.48.0", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.53.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" +dependencies = [ + "base64 0.22.1", + "block2 0.6.2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.60.2", + "winnow 0.7.13", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.13", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.13", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.106", + "winnow 0.7.13", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b4f992f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,47 @@ +[workspace] +members = [ + "crates/borders-core", + "crates/borders-desktop", + "crates/borders-wasm", + "crates/borders-server", +] +resolver = "2" + +[workspace.package] +authors = ["Xevion"] +edition = "2024" +version = "0.6.1" + +# Enable a small amount of optimization in the dev profile. +[profile.dev] +opt-level = 1 + +# Enable a large amount of optimization in the dev profile for dependencies. +[profile.dev.package."*"] +opt-level = 3 + +# Enable more optimization in the release profile at the cost of compile time. +[profile.release] +# Compile the entire crate as one unit. +# Slows compile times, marginal improvements. +codegen-units = 1 +# Do a second optimization pass over the entire program, including dependencies. +# Slows compile times, marginal improvements. +lto = "thin" + +# Development profile for WASM builds (faster compile times) +[profile.wasm-dev] +inherits = "dev" +opt-level = 1 +panic = "abort" + +# Size optimization profile for WASM builds +[profile.wasm-release] +inherits = "release" +incremental = false +debug = false +opt-level = "s" # Optimize for size +lto = true # Link-time optimization +codegen-units = 1 # Single codegen unit for better optimization +panic = "abort" # Smaller panic implementation +strip = true # Remove debug symbols diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..877cb88 --- /dev/null +++ b/Justfile @@ -0,0 +1,92 @@ +set shell := ["powershell"] + +default: + just --list + +check: + @echo "Running clippy (native)..." + @cargo clippy --all-targets --all-features --workspace -- -D warnings + @echo "Running cargo check (native)..." + @cargo check --all-targets --all-features --workspace + @echo "Running clippy (wasm32-unknown-unknown)..." + @cargo clippy --target wasm32-unknown-unknown --all-features -p borders-wasm -- -D warnings + @echo "Running cargo check (wasm32-unknown-unknown)..." + @cargo check --target wasm32-unknown-unknown --all-features -p borders-wasm + @echo "Running cargo machete..." + @cargo machete --with-metadata + @echo "All checks passed" + +check-ts: + @just _wasm-build wasm-dev + @echo "Running frontend checks..." + @pnpm run -C frontend check + +fix: + @echo "Running cargo fix..." + cargo fix --all-targets --all-features --workspace --allow-dirty + +wasm-dev: wasm-dev-build + pnpm -C frontend dev:browser --port 1421 + +# Build WASM with the specified profile (wasm-dev or wasm-release) +_wasm-build profile: + @$profile = "{{ profile }}"; \ + $wasmFile = "target/wasm32-unknown-unknown/$profile/borders_wasm.wasm"; \ + $pkgJs = "pkg/borders.js"; \ + $pkgWasm = "pkg/borders_bg.wasm"; \ + $frontendPkgJs = "frontend/pkg/borders.js"; \ + $frontendPkgWasm = "frontend/pkg/borders_bg.wasm"; \ + $beforeTime = if (Test-Path $wasmFile) { (Get-Item $wasmFile).LastWriteTime } else { $null }; \ + cargo build -p borders-wasm --profile $profile --target wasm32-unknown-unknown; \ + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; \ + $afterTime = if (Test-Path $wasmFile) { (Get-Item $wasmFile).LastWriteTime } else { $null }; \ + $wasRebuilt = ($beforeTime -eq $null) -or ($afterTime -ne $beforeTime); \ + $pkgExists = (Test-Path $pkgJs) -and (Test-Path $pkgWasm); \ + $frontendPkgExists = (Test-Path $frontendPkgJs) -and (Test-Path $frontendPkgWasm); \ + $isRelease = $profile -eq "wasm-release"; \ + if ($wasRebuilt -or -not $pkgExists -or ($isRelease -and -not $frontendPkgExists)) { \ + Write-Host "Running wasm-bindgen..."; \ + wasm-bindgen --out-dir pkg --out-name borders --target web $wasmFile; \ + if ($isRelease) { \ + Write-Host "Running wasm-opt..."; \ + wasm-opt -Oz --enable-bulk-memory --enable-threads --all-features pkg/borders_bg.wasm -o pkg/borders_bg.wasm; \ + }; \ + } else { \ + Write-Host "WASM not rebuilt, skipping wasm-bindgen"; \ + } \ + New-Item -ItemType Directory -Force -Path 'frontend/pkg' | Out-Null; \ + Copy-Item -Recurse -Force 'pkg/*' 'frontend/pkg/'; \ + if ($isRelease) { \ + Write-Host "Running frontend build..."; \ + pnpm -C frontend build:browser; \ + }; \ + +# Development WASM build, unoptimized +wasm-dev-build: + @just _wasm-build wasm-dev + +# Release WASM build, optimized +wasm-release-build: + @just _wasm-build wasm-release + +wasm-release: wasm-release-build + @echo "Visit http://localhost:8080 to play" + caddy file-server --listen :8080 --root frontend/dist/browser/client --browse + +desktop-release: + cargo tauri build + target/release/borders-desktop.exe + +desktop-dev: + cargo tauri build --debug + target/debug/borders-desktop.exe + +# Run release manager CLI (handles setup specially) +release *args: + @$firstArg = "{{ args }}".Split()[0]; \ + if ($firstArg -eq "setup") { \ + Write-Host "Installing release manager dependencies..."; \ + pnpm install -C scripts/release-manager; \ + } else { \ + pnpm --silent -C scripts/release-manager exec tsx cli.ts {{ args }}; \ + } \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f948eca --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright Ā© 2025 Ryan Walters. All Rights Reserved. + +This software and associated documentation files (the "Software") are proprietary +and confidential. Unauthorized copying, modification, distribution, or use of this +Software, via any medium, is strictly prohibited without the express written +permission of the copyright holder. + +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/crates/borders-core/Cargo.toml b/crates/borders-core/Cargo.toml new file mode 100644 index 0000000..2f74978 --- /dev/null +++ b/crates/borders-core/Cargo.toml @@ -0,0 +1,89 @@ +[package] +name = "borders-core" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[package.metadata.cargo-machete] +ignored = ["serde_bytes", "chrono"] + +[features] +default = ["ui"] +ui = [] + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +bevy_ecs = { version = "0.17", default-features = false, features = ["std"] } +bincode = { version = "2.0.1", features = ["serde"] } +flume = "0.11" +futures = "0.3" +futures-lite = "2.6.1" +glam = "0.30" +hex = "0.4" +hmac = "0.12" +image = "0.25" +once_cell = "1.20" +rand = "0.9" +serde = { version = "1.0", features = ["derive", "rc"] } +slotmap = "1.0" +serde_bytes = "0.11" +serde_json = "1.0" +sha2 = "0.10" +tracing = "0.1" +url = "2.5.0" +web-transport = "0.9" + +# Target-specific dependencies to keep WASM builds compatible +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ + "rt-multi-thread", + "macros", + "time", + "io-util", + "sync", +] } +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", + "brotli", + "gzip", + "deflate", + "zstd", +] } +hickory-resolver = { version = "0.25", features = [ + "tls-ring", + "https-ring", + "quic-ring", + "h3-ring", + "webpki-roots", +] } +uuid = { version = "1.11", features = ["v4", "serde"] } +machineid-rs = "1.2" +directories = "5.0" +rustls-pemfile = "2.2.0" +ring = "0.17.14" +pem = "3.0.5" +sysinfo = "0.33" + +[target.'cfg(windows)'.dependencies] +winreg = "0.52" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1", features = ["rt", "macros", "time", "io-util"] } +reqwest = { version = "0.12", default-features = false, features = ["json"] } +uuid = { version = "1.11", features = ["v4", "serde", "js"] } +js-sys = "0.3" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +gloo-timers = { version = "0.3", features = ["futures"] } +web-sys = { version = "0.3", features = [ + "BroadcastChannel", + "MessageEvent", + "Navigator", + "Window", +] } +web-time = "1.1" + +[build-dependencies] +chrono = "0.4" diff --git a/crates/borders-core/assets/maps/World.json b/crates/borders-core/assets/maps/World.json new file mode 100644 index 0000000..d4ff1ee --- /dev/null +++ b/crates/borders-core/assets/maps/World.json @@ -0,0 +1,76 @@ +{ + "tiles": [ + { + "color": "#000000", + "name": "Water", + "colorBase": "water", + "colorVariant": 4, + "conquerable": false, + "navigable": true + }, + { + "color": "#222222", + "name": "Water", + "colorBase": "water", + "colorVariant": 6, + "conquerable": false, + "navigable": true + }, + { + "color": "#555555", + "name": "Water", + "colorBase": "water", + "colorVariant": 12, + "conquerable": false, + "navigable": true + }, + { + "color": "#777777", + "name": "Water", + "colorBase": "water", + "colorVariant": 14, + "conquerable": false, + "navigable": true + }, + { + "color": "#999999", + "name": "Land", + "colorBase": "mountain", + "colorVariant": 5, + "conquerable": true, + "navigable": false, + "expansionCost": 80, + "expansionTime": 80 + }, + { + "color": "#BBBBBB", + "name": "Land", + "colorBase": "mountain", + "colorVariant": 9, + "conquerable": true, + "navigable": false, + "expansionCost": 70, + "expansionTime": 70 + }, + { + "color": "#DDDDDD", + "name": "Land", + "colorBase": "grass", + "colorVariant": 9, + "conquerable": true, + "navigable": false, + "expansionCost": 60, + "expansionTime": 60 + }, + { + "color": "#FFFFFF", + "name": "Land", + "colorBase": "grass", + "colorVariant": 6, + "conquerable": true, + "navigable": false, + "expansionCost": 50, + "expansionTime": 50 + } + ] +} diff --git a/crates/borders-core/assets/maps/World.png b/crates/borders-core/assets/maps/World.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaa74c1d2d095c46b3fc1fa0cdac3b22f8e4e52 GIT binary patch literal 189026 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 SCjdpr_|ffxig3LGEzy>ixo6by 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∨#UfA!jG4D}t@$4U);m^2$b^j`* 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 literal 0 HcmV?d00001 diff --git a/crates/borders-core/build.rs b/crates/borders-core/build.rs new file mode 100644 index 0000000..3171d78 --- /dev/null +++ b/crates/borders-core/build.rs @@ -0,0 +1,28 @@ +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + // Get the workspace root (two levels up from borders-core) + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let workspace_root = manifest_dir.parent().unwrap().parent().unwrap(); + + // Read git commit from .source-commit file + let source_commit_path = workspace_root.join(".source-commit"); + let git_commit = if source_commit_path.exists() { + fs::read_to_string(&source_commit_path).unwrap_or_else(|_| "unknown".to_string()).trim().to_string() + } else { + // Fallback to git command if file doesn't exist (local development) + std::process::Command::new("git").args(["rev-parse", "HEAD"]).current_dir(workspace_root).output().ok().and_then(|output| if output.status.success() { String::from_utf8(output.stdout).ok() } else { None }).map(|s| s.trim().to_string()).unwrap_or_else(|| "unknown".to_string()) + }; + + // Get current build time in UTC + let build_time = chrono::Utc::now().to_rfc3339(); + + // Set environment variables for compile-time access + println!("cargo:rustc-env=BUILD_GIT_COMMIT={}", git_commit); + println!("cargo:rustc-env=BUILD_TIME={}", build_time); + + // Re-run if .source-commit changes + println!("cargo:rerun-if-changed={}", source_commit_path.display()); +} diff --git a/crates/borders-core/src/app.rs b/crates/borders-core/src/app.rs new file mode 100644 index 0000000..64e50a4 --- /dev/null +++ b/crates/borders-core/src/app.rs @@ -0,0 +1,140 @@ +//! Minimal ECS app wrapper to replace Bevy's App + +use bevy_ecs::message::{Message, Messages}; +use bevy_ecs::prelude::*; +use bevy_ecs::schedule::{IntoScheduleConfigs, ScheduleLabel, Schedules}; +use bevy_ecs::system::ScheduleSystem; +use std::fmt::Debug; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Startup; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Update; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Last; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Fixed; + +pub struct App { + world: World, +} + +impl App { + pub fn new() -> Self { + let mut world = World::new(); + + // Initialize schedules + let mut schedules = Schedules::new(); + schedules.insert(Schedule::new(Startup)); + schedules.insert(Schedule::new(Update)); + schedules.insert(Schedule::new(Last)); + schedules.insert(Schedule::new(Fixed)); + + world.insert_resource(schedules); + + Self { world } + } + + pub fn world(&self) -> &World { + &self.world + } + + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + pub fn insert_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_resource(resource); + self + } + + pub fn init_resource(&mut self) -> &mut Self { + self.world.init_resource::(); + self + } + + pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_non_send_resource(resource); + self + } + + pub fn add_message(&mut self) -> &mut Self { + if !self.world.contains_resource::>() { + self.world.init_resource::>(); + + // Add system to update this message type each frame + self.add_systems(Last, |mut messages: ResMut>| { + messages.update(); + }); + } + self + } + + pub fn add_systems(&mut self, schedule: impl ScheduleLabel, systems: impl IntoScheduleConfigs) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if let Some(schedule_inst) = schedules.get_mut(schedule) { + schedule_inst.add_systems(systems); + } + self + } + + pub fn update(&mut self) { + // Remove schedules temporarily to avoid resource_scope conflicts + let mut schedules = self.world.remove_resource::().unwrap(); + + // Run Update schedule + if let Some(schedule) = schedules.get_mut(Update) { + schedule.run(&mut self.world); + } + + // Run Last schedule (includes event updates) + if let Some(schedule) = schedules.get_mut(Last) { + schedule.run(&mut self.world); + } + + // Re-insert schedules + self.world.insert_resource(schedules); + } + + pub fn run_startup(&mut self) { + // Remove schedules temporarily to avoid resource_scope conflicts + let mut schedules = self.world.remove_resource::().unwrap(); + + // Run Startup schedule + if let Some(schedule) = schedules.get_mut(Startup) { + schedule.run(&mut self.world); + } + + // Re-insert schedules + self.world.insert_resource(schedules); + } + + pub fn finish(&mut self) { + // Finalize schedules + let mut schedules = self.world.remove_resource::().unwrap(); + + for (_, schedule) in schedules.iter_mut() { + schedule.graph_mut().initialize(&mut self.world); + } + + self.world.insert_resource(schedules); + } + + pub fn cleanup(&mut self) { + // Any cleanup needed before running + } +} + +impl Default for App { + fn default() -> Self { + Self::new() + } +} + +/// Plugin trait for modular setup +pub trait Plugin { + fn build(&self, app: &mut App); +} diff --git a/crates/borders-core/src/build_info.rs b/crates/borders-core/src/build_info.rs new file mode 100644 index 0000000..87f15f2 --- /dev/null +++ b/crates/borders-core/src/build_info.rs @@ -0,0 +1,21 @@ +//! Build metadata injected at compile time + +/// The version of the application from Cargo.toml +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The git commit hash from .source-commit file or git command +pub const GIT_COMMIT: &str = env!("BUILD_GIT_COMMIT"); + +/// The build timestamp in RFC3339 format (UTC) +pub const BUILD_TIME: &str = env!("BUILD_TIME"); + +/// Get the git commit hash (short form, first 7 characters) +pub fn git_commit_short() -> &'static str { + let full = GIT_COMMIT; + if full.len() >= 7 { &full[..7] } else { full } +} + +/// Full build information formatted as a string +pub fn info() -> String { + format!("Iron Borders v{} ({})\nBuilt: {}", VERSION, git_commit_short(), BUILD_TIME) +} diff --git a/crates/borders-core/src/constants.rs b/crates/borders-core/src/constants.rs new file mode 100644 index 0000000..5024806 --- /dev/null +++ b/crates/borders-core/src/constants.rs @@ -0,0 +1,8 @@ +/// Game tick interval in milliseconds (10 TPS = 100ms per turn) +pub const TICK_INTERVAL: u64 = 100; + +/// Number of bot players +pub const BOT_COUNT: usize = 100; + +/// Spawn timeout duration in seconds +pub const SPAWN_TIMEOUT_SECS: f32 = 2.0; diff --git a/crates/borders-core/src/game/action.rs b/crates/borders-core/src/game/action.rs new file mode 100644 index 0000000..f6fcc3f --- /dev/null +++ b/crates/borders-core/src/game/action.rs @@ -0,0 +1,40 @@ +//! Game action system +//! +//! This module defines the core action types that can be performed in the game. +//! Actions represent discrete game events that can be initiated by both human players +//! and AI bots. They are processed deterministically during turn execution. + +use serde::{Deserialize, Serialize}; + +/// Core game action type +/// +/// This enum represents all possible actions that can be performed in the game. +/// Unlike `Intent`, which is a network-layer wrapper, `GameAction` is the actual +/// game-level operation. +/// +/// Actions can originate from: +/// - Human players (via input systems → intents → network) +/// - AI bots (calculated deterministically during turn execution) +/// +/// All actions are processed in a deterministic order during `GameInstance::execute_turn()`. +#[derive(Debug, Clone, Serialize, Deserialize, bincode::Encode, bincode::Decode)] +pub enum GameAction { + /// Spawn a player's initial territory at a specific tile + /// + /// TODO: Remove this, it should be handled by a Turn0 special configuration AND the Intent::SetSpawn + Spawn { player_id: u16, tile_index: u32 }, + /// Attack a target tile with a percentage of the player's total troops + Attack { player_id: u16, target_tile: u32, troops_ratio: f32 }, + /// Launch a transport ship to attack across water + LaunchShip { + player_id: u16, + target_tile: u32, + /// Troops as a percentage (0-100) to avoid float precision issues + troops_percent: u32, + }, + // Future action types: + // BuildStructure { player_id: u16, tile_index: u32, structure_type: StructureType }, + // LaunchNuke { player_id: u16, target_tile: u32 }, + // RequestAlliance { player_id: u16, target_player: u16 }, + // DeclareWar { player_id: u16, target_player: u16 }, +} diff --git a/crates/borders-core/src/game/borders.rs b/crates/borders-core/src/game/borders.rs new file mode 100644 index 0000000..ba769d3 --- /dev/null +++ b/crates/borders-core/src/game/borders.rs @@ -0,0 +1,211 @@ +/// Border tile management +/// +/// This module manages border tiles for all players. A border tile is a tile +/// adjacent to a tile with a different owner. Borders are used for: +/// - Attack targeting (attacks expand from border tiles) +/// - UI rendering (show player borders on the map) +/// - Ship launching (find coastal borders for naval operations) +use std::collections::HashSet; + +use bevy_ecs::prelude::*; + +use crate::game::{ + GameInstance, + player::{BorderTiles, PlayerId}, + territory_manager::TerritoryManager, +}; + +/// Result of a border transition +#[derive(Debug)] +pub struct BorderTransitionResult { + /// Tiles that became interior (not borders anymore) + pub territory: Vec, + /// Tiles that are now attacker borders + pub attacker: Vec, + /// Tiles that are now defender borders + pub defender: Vec, +} + +/// Manages border tiles for all players +/// +/// A border tile is a tile adjacent to a tile with a different owner. +/// This resource maintains efficient lookup of border tiles for attack +/// targeting and UI rendering. +/// +/// # Border Detection Algorithm +/// +/// A tile is a border if any of its 4-directional neighbors has a different owner: +/// - Water tiles have no owner and are never borders +/// - Unclaimed tiles have no owner and are never borders +/// - A tile surrounded entirely by same-owner tiles is interior (not a border) +/// +/// # Future ECS Migration +/// +/// This will eventually become a `BorderTiles` component on player entities, +/// with a system to update borders after territory changes. +#[derive(Resource)] +pub struct BorderManager { + border_tiles: Vec>, +} + +impl Default for BorderManager { + fn default() -> Self { + Self::new() + } +} + +impl BorderManager { + pub fn new() -> Self { + Self { border_tiles: Vec::new() } + } + + /// Resets the border manager + /// + /// Should only be called when a new game is started + pub fn reset(&mut self, _width: u32, _height: u32, player_count: usize) { + self.border_tiles = vec![HashSet::new(); player_count]; + } + + /// Update borders after territory changes (e.g., conquest) + /// + /// A tile is a border if any neighbor has a different owner. + /// This method checks all affected tiles (conquered tiles + their neighbors) + /// and updates border sets accordingly. + pub fn update_after_conquest(&mut self, tiles: &HashSet, attacker: usize, defender: Option, territory_manager: &TerritoryManager) -> BorderTransitionResult { + let _ = tracing::debug_span!("border_transition"); + let _span = tracing::trace_span!("border_transition", tile_count = tiles.len()).entered(); + + let mut result = BorderTransitionResult { territory: Vec::new(), attacker: Vec::new(), defender: Vec::new() }; + + // Collect all tiles to check: conquered tiles + all their neighbors + let mut tiles_to_check = HashSet::new(); + + for &tile in tiles { + tiles_to_check.insert(tile); + + // Add all neighbors + territory_manager.on_neighbor_indices(tile, |neighbor| { + tiles_to_check.insert(neighbor); + }); + } + + // Remove conquered tiles from defender borders + if let Some(defender_id) = defender { + for &tile in tiles { + self.border_tiles[defender_id].remove(&tile); + } + } + + // Update borders for all affected tiles + for &tile in &tiles_to_check { + // Skip if tile has no owner (water or unclaimed) + if !territory_manager.has_owner(tile) { + continue; + } + + let owner_id = territory_manager.get_owner(tile); + let owner = owner_id as usize; + + // Determine if this tile is a border + let is_border = Self::is_border_tile(tile, owner_id, territory_manager); + + if is_border { + // Add to border set if not already present + if self.border_tiles[owner].insert(tile) { + // Track for result + if owner == attacker { + result.attacker.push(tile); + } else if Some(owner) == defender { + result.defender.push(tile); + } + } + } else { + // Remove from border set if present + if self.border_tiles[owner].remove(&tile) { + // Tile became interior + if owner == attacker { + result.territory.push(tile); + } + } + } + } + + if tiles.len() > 50 { + tracing::trace!(tile_count = tiles.len(), "Large border transition"); + } + + result + } + + /// Check if a tile is a border tile using simple neighbor logic + /// + /// A tile is a border if it has any neighbor with a different owner + fn is_border_tile(tile: usize, owner: u16, territory_manager: &TerritoryManager) -> bool { + let mut is_border = false; + + territory_manager.on_neighbor_indices(tile, |neighbor| { + if territory_manager.get_owner(neighbor) != owner { + is_border = true; + } + }); + + is_border + } + + /// Gets the border tiles of a player + pub fn get_border_tiles(&self, player: usize) -> &HashSet { + &self.border_tiles[player] + } +} + +/// Update all player borders based on territory changes (batched system) +/// +/// This system runs once per turn AFTER all territory changes (conquests, spawns, ships). +/// It drains the TerritoryManager's change buffer and updates borders for all affected players. +pub fn update_player_borders_system(mut players: Query<(&PlayerId, &mut BorderTiles)>, mut game_instance: ResMut) { + if !game_instance.territory_manager.has_changes() { + return; // Early exit - no work needed + } + + // Collect all changed tiles at once + let changed_tiles: HashSet = game_instance.territory_manager.iter_changes().collect(); + + // Build affected tiles (changed + all neighbors) + let mut affected_tiles = HashSet::with_capacity(changed_tiles.len() * 5); + for &tile in &changed_tiles { + affected_tiles.insert(tile); + game_instance.territory_manager.on_neighbor_indices(tile, |n| { + affected_tiles.insert(n); + }); + } + + // Update each player's borders (dual-write to resource and components) + for (player_id, mut component_borders) in &mut players { + update_borders_for_player(player_id.0, &mut component_borders.0, &affected_tiles, &game_instance.territory_manager); + + // Dual-write: sync to resource for backward compatibility + game_instance.border_manager.border_tiles[player_id.0 as usize] = component_borders.0.clone(); + } +} + +/// Update borders for a single player based on affected tiles +fn update_borders_for_player(player_id: u16, borders: &mut HashSet, affected_tiles: &HashSet, territory: &TerritoryManager) { + for &tile in affected_tiles { + let owner = territory.get_owner(tile); + + // Skip tiles we don't own + if owner != player_id { + borders.remove(&tile); + continue; + } + + // Check if it's a border (early exit when different owner found) + let is_border = territory.any_neighbor_has_different_owner(tile, player_id); + + if is_border { + borders.insert(tile); + } else { + borders.remove(&tile); + } + } +} diff --git a/crates/borders-core/src/game/bot.rs b/crates/borders-core/src/game/bot.rs new file mode 100644 index 0000000..6a17a8a --- /dev/null +++ b/crates/borders-core/src/game/bot.rs @@ -0,0 +1,521 @@ +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +use crate::game::action::GameAction; +use crate::game::borders::BorderManager; +use crate::game::player::Player; +use crate::game::territory_manager::TerritoryManager; + +/// Simple bot AI +pub struct BotPlayer { + last_action_tick: u64, + action_cooldown: u64, +} + +impl Default for BotPlayer { + fn default() -> Self { + Self::new() + } +} + +impl BotPlayer { + pub fn new() -> Self { + let mut rng = rand::rng(); + Self { + last_action_tick: 0, + action_cooldown: rng.random_range(0..10), // 0-1 seconds + } + } + + /// Tick the bot AI - now deterministic based on turn number and RNG seed + pub fn tick(&mut self, turn_number: u64, player: &Player, territory_manager: &TerritoryManager, border_manager: &BorderManager, rng_seed: u64) -> Option { + // Only act every few ticks + if turn_number < self.last_action_tick + self.action_cooldown { + return None; + } + + self.last_action_tick = turn_number; + + // Deterministic RNG based on turn number, player ID, and global seed + let seed = rng_seed.wrapping_add(turn_number).wrapping_add(player.id as u64); + let mut rng = StdRng::seed_from_u64(seed); + self.action_cooldown = rng.random_range(3..15); + + // Decide action: expand into wilderness or attack a neighbor + let action_type: f32 = rng.random(); + + let action = if action_type < 0.6 { + // Expand into wilderness (60% chance) + self.expand_wilderness(player, territory_manager, border_manager, &mut rng) + } else { + // Attack a neighbor (40% chance) + self.attack_neighbor(player, territory_manager, border_manager, &mut rng) + }; + + let _span = tracing::debug_span!("bot_tick", player_id = player.id, has_action = action.is_some()).entered(); + action + } + + /// Expand into unclaimed territory + fn expand_wilderness(&self, player: &Player, territory_manager: &TerritoryManager, border_manager: &BorderManager, rng: &mut StdRng) -> Option { + let border_tiles = border_manager.get_border_tiles(player.id); + + // Find a valid, unclaimed neighbor tile to attack + for &tile in border_tiles { + let x = tile as u32 % territory_manager.width(); + let y = tile as u32 / territory_manager.width(); + + let neighbors = [(x > 0).then_some(tile - 1), (x < territory_manager.width() - 1).then_some(tile + 1), (y > 0).then_some(tile - territory_manager.width() as usize), (y < territory_manager.height() - 1).then_some(tile + territory_manager.width() as usize)]; + + for neighbor in neighbors.iter().flatten() { + if !territory_manager.has_owner(*neighbor) && !territory_manager.is_water(*neighbor) { + // Found unclaimed land + let troop_percentage: f32 = rng.random_range(0.1..0.3); + return Some(GameAction::Attack { player_id: player.id as u16, target_tile: *neighbor as u32, troops_ratio: troop_percentage }); + } + } + } + + tracing::trace!(player_id = player.id, "No wilderness target found"); + None + } + + /// Attack a neighboring player + fn attack_neighbor(&self, player: &Player, territory_manager: &TerritoryManager, border_manager: &BorderManager, rng: &mut StdRng) -> Option { + // Find neighboring players + let mut neighbors = std::collections::HashSet::new(); + let border_tiles = border_manager.get_border_tiles(player.id); + + for &tile in border_tiles { + let x = tile as u32 % territory_manager.width(); + let y = tile as u32 / territory_manager.width(); + + // Check all neighbors + if x > 0 { + let neighbor = tile - 1; + let ownership = territory_manager.get_ownership(neighbor); + if let Some(nation_id) = ownership.nation_id() + && nation_id != player.id as u16 + { + neighbors.insert(nation_id as usize); + } + } + if x < territory_manager.width() - 1 { + let neighbor = tile + 1; + let ownership = territory_manager.get_ownership(neighbor); + if let Some(nation_id) = ownership.nation_id() + && nation_id != player.id as u16 + { + neighbors.insert(nation_id as usize); + } + } + if y > 0 { + let neighbor = tile - territory_manager.width() as usize; + let ownership = territory_manager.get_ownership(neighbor); + if let Some(nation_id) = ownership.nation_id() + && nation_id != player.id as u16 + { + neighbors.insert(nation_id as usize); + } + } + if y < territory_manager.height() - 1 { + let neighbor = tile + territory_manager.width() as usize; + let ownership = territory_manager.get_ownership(neighbor); + if let Some(nation_id) = ownership.nation_id() + && nation_id != player.id as u16 + { + neighbors.insert(nation_id as usize); + } + } + } + + if neighbors.is_empty() { + return None; + } + + // Pick a random neighbor to attack + let neighbor_vec: Vec<_> = neighbors.into_iter().collect(); + let target_id = neighbor_vec[rng.random_range(0..neighbor_vec.len())]; + + // To attack a player, we need to pick a specific tile. + // Let's find a border tile of the target player that is adjacent to us. + let target_border = border_manager.get_border_tiles(target_id); + for &target_tile in target_border { + let x = target_tile as u32 % territory_manager.width(); + let y = target_tile as u32 / territory_manager.width(); + let neighbor_indices = [(x > 0).then_some(target_tile - 1), (x < territory_manager.width() - 1).then_some(target_tile + 1), (y > 0).then_some(target_tile - territory_manager.width() as usize), (y < territory_manager.height() - 1).then_some(target_tile + territory_manager.width() as usize)]; + + for &neighbor_idx in neighbor_indices.iter().flatten() { + if territory_manager.get_owner(neighbor_idx) == player.id as u16 { + // This is a valid attack target + let troop_percentage: f32 = rng.random_range(0.2..0.5); + return Some(GameAction::Attack { player_id: player.id as u16, target_tile: target_tile as u32, troops_ratio: troop_percentage }); + } + } + } + + None + } +} + +/// Minimum distance (in tiles) between any two spawn points +/// This ensures players and bots don't spawn too close together +const MIN_SPAWN_DISTANCE: f32 = 70.0; + +/// Absolute minimum spawn distance to prevent exact overlaps +/// Used as final fallback when map is very crowded +const ABSOLUTE_MIN_DISTANCE: f32 = 5.0; + +/// Distance reduction factor per adaptive wave (15% reduction) +const DISTANCE_REDUCTION_FACTOR: f32 = 0.85; + +/// Spatial grid for fast spawn collision detection +/// Divides map into cells for O(1) neighbor queries instead of O(n) +struct SpawnGrid { + grid: std::collections::HashMap<(i32, i32), Vec>, + cell_size: f32, + map_width: u32, +} + +impl SpawnGrid { + fn new(cell_size: f32, map_width: u32) -> Self { + Self { grid: std::collections::HashMap::new(), cell_size, map_width } + } + + fn insert(&mut self, tile: usize) { + let cell = self.tile_to_cell(tile); + self.grid.entry(cell).or_default().push(tile); + } + + fn tile_to_cell(&self, tile: usize) -> (i32, i32) { + let x = (tile as u32 % self.map_width) as f32; + let y = (tile as u32 / self.map_width) as f32; + ((x / self.cell_size) as i32, (y / self.cell_size) as i32) + } + + fn has_nearby(&self, tile: usize, radius: f32, map_width: u32) -> bool { + let cell = self.tile_to_cell(tile); + let cell_radius = (radius / self.cell_size).ceil() as i32; + + for dx in -cell_radius..=cell_radius { + for dy in -cell_radius..=cell_radius { + let check_cell = (cell.0 + dx, cell.1 + dy); + if let Some(tiles) = self.grid.get(&check_cell) { + for &existing_tile in tiles { + if calculate_tile_distance(tile, existing_tile, map_width) < radius { + return true; + } + } + } + } + } + false + } +} + +/// Calculate Euclidean distance between two tiles +fn calculate_tile_distance(tile1: usize, tile2: usize, map_width: u32) -> f32 { + let x1 = (tile1 as u32 % map_width) as f32; + let y1 = (tile1 as u32 / map_width) as f32; + let x2 = (tile2 as u32 % map_width) as f32; + let y2 = (tile2 as u32 / map_width) as f32; + + let dx = x1 - x2; + let dy = y1 - y2; + (dx * dx + dy * dy).sqrt() +} + +/// Manager for bot AI state and decision-making +/// +/// BotManager is part of GameInstance and handles all bot decision-making +/// in a deterministic way. Unlike the old Bevy-based bot system, this is +/// part of the core game state and executes during turn processing. +pub struct BotManager { + bots: Vec, + bot_player_ids: Vec, +} + +impl BotManager { + /// Create a new BotManager with the specified number of bots + pub fn new(bot_count: usize, human_player_count: usize) -> Self { + let bots = (0..bot_count).map(|_| BotPlayer::new()).collect(); + + // Bot player IDs start after human players + // Human player is ID 0, so first bot is ID 1 + let first_bot_id = human_player_count; + let bot_player_ids = (first_bot_id..(first_bot_id + bot_count)).collect(); + + Self { bots, bot_player_ids } + } + + /// Get the number of bots + pub fn bot_count(&self) -> usize { + self.bots.len() + } + + /// Get bot player IDs + pub fn bot_player_ids(&self) -> &[usize] { + &self.bot_player_ids + } + + /// Calculate initial bot spawn positions (first pass) + /// + /// Places bots at random valid locations with adaptive spacing. + /// Uses spatial grid for O(1) neighbor checks and adaptively reduces + /// minimum distance when map becomes crowded. + /// + /// Guarantees all bots spawn (no silent drops). This is deterministic based on rng_seed. + /// + /// Returns Vec<(player_id, tile_index)> for each bot + pub fn calculate_initial_spawns(&self, territory_manager: &TerritoryManager, rng_seed: u64) -> Vec<(usize, usize)> { + let width = territory_manager.width(); + let height = territory_manager.height(); + let map_size = (width * height) as usize; + + let mut spawn_positions = Vec::with_capacity(self.bot_player_ids.len()); + let mut grid = SpawnGrid::new(MIN_SPAWN_DISTANCE, width); + let mut current_min_distance = MIN_SPAWN_DISTANCE; + + for (bot_index, &player_id) in self.bot_player_ids.iter().enumerate() { + // Deterministic RNG for spawn location + let seed = rng_seed.wrapping_add(player_id as u64).wrapping_add(bot_index as u64); + let mut rng = StdRng::seed_from_u64(seed); + + let mut placed = false; + + // Try with current minimum distance + while !placed && current_min_distance >= ABSOLUTE_MIN_DISTANCE { + // Phase 1: Random sampling (1000 attempts) + for _ in 0..1000 { + let tile = rng.random_range(0..map_size); + + // Check if tile is valid land + if territory_manager.has_owner(tile) || territory_manager.is_water(tile) { + continue; + } + + // Check distance using spatial grid (O(1) instead of O(n)) + if !grid.has_nearby(tile, current_min_distance, width) { + spawn_positions.push((player_id, tile)); + grid.insert(tile); + placed = true; + break; + } + } + + // Phase 2: Grid-guided fallback (if random sampling failed) + if !placed { + // Try a systematic grid search with stride + let stride = (current_min_distance * 0.8) as usize; + let mut attempts = 0; + for y in (0..height as usize).step_by(stride.max(1)) { + for x in (0..width as usize).step_by(stride.max(1)) { + let tile = y * width as usize + x; + if tile >= map_size { + continue; + } + + if territory_manager.has_owner(tile) || territory_manager.is_water(tile) { + continue; + } + + if !grid.has_nearby(tile, current_min_distance, width) { + spawn_positions.push((player_id, tile)); + grid.insert(tile); + placed = true; + break; + } + + attempts += 1; + if attempts > 200 { + break; + } + } + if placed { + break; + } + } + } + + // Phase 3: Reduce minimum distance and retry + if !placed { + current_min_distance *= DISTANCE_REDUCTION_FACTOR; + if bot_index % 100 == 0 && current_min_distance < MIN_SPAWN_DISTANCE { + tracing::debug!("Adaptive spawn: reduced min_distance to {:.1} for bot {}", current_min_distance, bot_index); + } + } + } + + // Final fallback: Place at any valid land tile (guaranteed) + if !placed { + for _ in 0..10000 { + let tile = rng.random_range(0..map_size); + if !territory_manager.has_owner(tile) && !territory_manager.is_water(tile) { + spawn_positions.push((player_id, tile)); + grid.insert(tile); + placed = true; + tracing::warn!("Bot {} placed with fallback (no distance constraint)", player_id); + break; + } + } + } + + if !placed { + tracing::error!("Failed to place bot {} after all attempts", player_id); + } + } + + spawn_positions + } + + /// Recalculate bot spawns considering player positions (second pass) + /// + /// For any bot that is too close to a player spawn, find a new position. + /// Uses adaptive algorithm with grid acceleration to guarantee all displaced + /// bots find new positions. This maintains determinism while ensuring proper spawn spacing. + /// + /// Arguments: + /// - `initial_bot_spawns`: Bot positions from first pass + /// - `player_spawns`: Human player spawn positions + /// - `territory_manager`: For checking valid tiles + /// - `rng_seed`: For deterministic relocation + /// + /// Returns updated Vec<(player_id, tile_index)> with relocated bots + pub fn recalculate_spawns_with_players(&self, initial_bot_spawns: Vec<(usize, usize)>, player_spawns: &[(usize, usize)], territory_manager: &TerritoryManager, rng_seed: u64) -> Vec<(usize, usize)> { + let width = territory_manager.width(); + let height = territory_manager.height(); + let map_size = (width * height) as usize; + + // Build spatial grid from player spawns and bots we're keeping + let mut grid = SpawnGrid::new(MIN_SPAWN_DISTANCE, width); + for &(_, tile) in player_spawns { + grid.insert(tile); + } + + // Identify bots that need relocation + let mut bots_to_relocate = Vec::new(); + let mut final_spawns = Vec::new(); + + for (player_id, tile) in initial_bot_spawns { + let mut needs_relocation = false; + + // Check distance from all player spawns using grid + for &(_, player_tile) in player_spawns { + if calculate_tile_distance(tile, player_tile, width) < MIN_SPAWN_DISTANCE { + needs_relocation = true; + break; + } + } + + if needs_relocation { + bots_to_relocate.push(player_id); + } else { + final_spawns.push((player_id, tile)); + grid.insert(tile); + } + } + + // Relocate bots using adaptive algorithm (same as calculate_initial_spawns) + let mut current_min_distance = MIN_SPAWN_DISTANCE; + + for (reloc_index, &player_id) in bots_to_relocate.iter().enumerate() { + let seed = rng_seed.wrapping_add(player_id as u64).wrapping_add(0xDEADBEEF); + let mut rng = StdRng::seed_from_u64(seed); + + let mut placed = false; + + // Try with current minimum distance + while !placed && current_min_distance >= ABSOLUTE_MIN_DISTANCE { + // Phase 1: Random sampling (1000 attempts) + for _ in 0..1000 { + let tile = rng.random_range(0..map_size); + + // Check if tile is valid land + if territory_manager.has_owner(tile) || territory_manager.is_water(tile) { + continue; + } + + // Check distance using spatial grid (includes players + placed bots) + if !grid.has_nearby(tile, current_min_distance, width) { + final_spawns.push((player_id, tile)); + grid.insert(tile); + placed = true; + break; + } + } + + // Phase 2: Grid-guided fallback + if !placed { + let stride = (current_min_distance * 0.8) as usize; + let mut attempts = 0; + for y in (0..height as usize).step_by(stride.max(1)) { + for x in (0..width as usize).step_by(stride.max(1)) { + let tile = y * width as usize + x; + if tile >= map_size { + continue; + } + + if territory_manager.has_owner(tile) || territory_manager.is_water(tile) { + continue; + } + + if !grid.has_nearby(tile, current_min_distance, width) { + final_spawns.push((player_id, tile)); + grid.insert(tile); + placed = true; + break; + } + + attempts += 1; + if attempts > 200 { + break; + } + } + if placed { + break; + } + } + } + + // Phase 3: Reduce minimum distance and retry + if !placed { + current_min_distance *= DISTANCE_REDUCTION_FACTOR; + if reloc_index % 50 == 0 && current_min_distance < MIN_SPAWN_DISTANCE { + tracing::debug!("Adaptive relocation: reduced min_distance to {:.1} for bot {}", current_min_distance, reloc_index); + } + } + } + + // Final fallback: Place at any valid land tile (guaranteed) + if !placed { + for _ in 0..10000 { + let tile = rng.random_range(0..map_size); + if !territory_manager.has_owner(tile) && !territory_manager.is_water(tile) { + final_spawns.push((player_id, tile)); + grid.insert(tile); + placed = true; + tracing::warn!("Bot {} relocated with fallback (no distance constraint)", player_id); + break; + } + } + } + + if !placed { + tracing::error!("Failed to relocate bot {} after all attempts", player_id); + } + } + + final_spawns + } + + /// Calculate action for a specific bot + /// + /// This is deterministic - same inputs = same output + pub fn calculate_action(&mut self, bot_index: usize, turn_number: u64, player: &Player, territory_manager: &TerritoryManager, border_manager: &BorderManager, rng_seed: u64) -> Option { + if bot_index >= self.bots.len() { + return None; + } + + self.bots[bot_index].tick(turn_number, player, territory_manager, border_manager, rng_seed) + } +} diff --git a/crates/borders-core/src/game/combat/active.rs b/crates/borders-core/src/game/combat/active.rs new file mode 100644 index 0000000..93c603d --- /dev/null +++ b/crates/borders-core/src/game/combat/active.rs @@ -0,0 +1,256 @@ +/// Active attacks management +/// +/// This module manages all ongoing attacks in the game. It provides efficient +/// lookup and coordination of attacks, ensuring proper merging of attacks on +/// the same target and handling counter-attacks. +use std::collections::HashSet; + +use bevy_ecs::prelude::*; +use slotmap::{SlotMap, new_key_type}; + +new_key_type! { + /// Unique key for identifying attacks in the SlotMap + pub struct AttackKey; +} + +use super::executor::{AttackConfig, AttackExecutor}; +use crate::game::rng::DeterministicRng; +use crate::game::{borders::BorderManager, player::Player, territory_manager::TerritoryManager}; + +/// Manages all active attacks in the game +/// +/// This resource tracks ongoing attacks and provides efficient lookup +/// by attacker/target relationships. Attacks progress over multiple turns +/// until they run out of troops or conquerable tiles. +/// +/// # Index Structures +/// +/// Multiple index structures are maintained for fast lookup: +/// - `player_index`: [attacker][target] → set of attack keys +/// - `unclaimed_index`: [attacker] → attack key for unclaimed territory +/// - `player_attack_list`: [player] → attacks where player is attacker +/// - `target_attack_list`: [player] → attacks where player is target +/// +/// Uses SlotMap for stable keys - no index shifting needed on removal. +/// Uses HashSet for O(1) lookups and removals. +#[derive(Resource)] +pub struct ActiveAttacks { + attacks: SlotMap, + player_index: Vec>>, // [attacker][target] -> set of attack keys + unclaimed_index: Vec>, // [attacker] -> attack key for unclaimed + player_attack_list: Vec>, // [player] -> set of attack keys where player is attacker + target_attack_list: Vec>, // [player] -> set of attack keys where player is target + width: u32, +} + +impl Default for ActiveAttacks { + fn default() -> Self { + Self::new() + } +} + +impl ActiveAttacks { + pub fn new() -> Self { + Self { attacks: SlotMap::with_key(), player_index: Vec::new(), unclaimed_index: Vec::new(), player_attack_list: Vec::new(), target_attack_list: Vec::new(), width: 0 } + } + + /// Initialize the attack handler + pub fn init(&mut self, max_players: usize, width: u32) { + self.attacks.clear(); + self.player_index = vec![vec![HashSet::new(); max_players]; max_players]; + self.unclaimed_index = vec![None; max_players]; + self.player_attack_list = vec![HashSet::new(); max_players]; + self.target_attack_list = vec![HashSet::new(); max_players]; + self.width = width; + } + + /// Schedule an attack on unclaimed territory + /// + /// If an attack on unclaimed territory already exists for this player, + /// the troops are added to it and borders are expanded. + #[allow(clippy::too_many_arguments)] + pub fn schedule_unclaimed(&mut self, player_id: usize, troops: f32, target_tile: usize, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, border_manager: &BorderManager, turn_number: u64, rng: &DeterministicRng) { + // Check if there's already an attack on unclaimed territory + if let Some(attack_key) = self.unclaimed_index[player_id] { + // Add troops to existing attack + self.attacks[attack_key].modify_troops(troops); + + // Add new borders to allow multi-region expansion + let borders = border_tiles.unwrap_or_else(|| border_manager.get_border_tiles(player_id)); + self.attacks[attack_key].add_borders(borders, territory_manager, self.width, rng); + return; + } + + // Create new attack + self.add_unclaimed(player_id, troops, target_tile, border_tiles, territory_manager, border_manager, turn_number, rng); + } + + /// Schedule an attack on another player + /// + /// Handles attack merging (if attacking same target) and counter-attacks + /// (opposite direction attacks are resolved first). + #[allow(clippy::too_many_arguments)] + pub fn schedule_attack(&mut self, player_id: usize, target_id: usize, target_tile: usize, mut troops: f32, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, border_manager: &BorderManager, turn_number: u64, rng: &DeterministicRng) { + // Check if there's already an attack on this target + if let Some(&attack_key) = self.player_index[player_id][target_id].iter().next() { + // Add troops to existing attack + self.attacks[attack_key].modify_troops(troops); + + // Add new borders to allow multi-region expansion + let borders = border_tiles.unwrap_or_else(|| border_manager.get_border_tiles(player_id)); + self.attacks[attack_key].add_borders(borders, territory_manager, self.width, rng); + return; + } + + // Check for counter-attacks (opposite direction) - prevent mutual attacks + while !self.player_index[target_id][player_id].is_empty() { + let opposite_key = *self.player_index[target_id][player_id].iter().next().unwrap(); + + if self.attacks[opposite_key].oppose(troops) { + // Counter-attack absorbed the new attack + return; + } + + // Counter-attack was defeated, deduct its troops from the new attack + troops -= self.attacks[opposite_key].get_troops(); + + // Remove the defeated counter-attack + self.remove_attack(opposite_key); + } + + // Create new attack + self.add_attack(player_id, target_id, troops, target_tile, border_tiles, territory_manager, border_manager, turn_number, rng); + } + + /// Tick all active attacks + /// + /// Progresses each attack by one turn. Attacks that run out of troops + /// or conquerable tiles are removed and their remaining troops are + /// returned to the attacking player. + pub fn tick(&mut self, players: &mut [Player], territory_manager: &mut TerritoryManager, border_manager: &mut BorderManager, rng: &DeterministicRng) { + let _span = tracing::trace_span!("attacks_tick", attack_count = self.attacks.len()).entered(); + + let mut attacks_to_remove = Vec::new(); + + for (attack_key, attack) in &mut self.attacks { + let _ = tracing::trace_span!("tick_attack"); + let should_continue = attack.tick(players, territory_manager, border_manager, self.width, rng); + + if !should_continue { + // Return remaining troops to player + let player_id = attack.player_id; + let remaining_troops = attack.get_troops(); + tracing::trace!(player_id, remaining_troops, "Attack completed"); + players[player_id].add_troops(remaining_troops); + + // Mark attack for removal + attacks_to_remove.push(attack_key); + } + } + + // Remove completed attacks + for attack_key in attacks_to_remove { + self.remove_attack(attack_key); + } + } + + /// Handle a tile being added to a player's territory + /// + /// Notifies all relevant attacks that territory has changed so they can + /// update their borders and targets. + pub fn handle_territory_add(&mut self, tile: usize, player_id: usize, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + // Notify all attacks where this player is the attacker + for &attack_key in &self.player_attack_list[player_id] { + self.attacks[attack_key].handle_player_tile_add(tile, territory_manager, self.width, rng); + } + + // Notify all attacks where this player is the target + for &attack_key in &self.target_attack_list[player_id] { + self.attacks[attack_key].handle_target_tile_add(tile, territory_manager, self.width, rng); + } + } + + /// Add an attack on unclaimed territory + #[allow(clippy::too_many_arguments)] + fn add_unclaimed(&mut self, player_id: usize, troops: f32, target_tile: usize, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, border_manager: &BorderManager, turn_number: u64, rng: &DeterministicRng) { + let attack = AttackExecutor::new(AttackConfig { player_id, target_id: None, troops, target_tile, border_tiles, territory_manager, border_manager, width: self.width, turn_number }, rng); + + let attack_key = self.attacks.insert(attack); + self.unclaimed_index[player_id] = Some(attack_key); + self.player_attack_list[player_id].insert(attack_key); + } + + /// Add an attack on a player + #[allow(clippy::too_many_arguments)] + fn add_attack(&mut self, player_id: usize, target_id: usize, troops: f32, target_tile: usize, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, border_manager: &BorderManager, turn_number: u64, rng: &DeterministicRng) { + let attack = AttackExecutor::new(AttackConfig { player_id, target_id: Some(target_id), troops, target_tile, border_tiles, territory_manager, border_manager, width: self.width, turn_number }, rng); + + let attack_key = self.attacks.insert(attack); + self.player_index[player_id][target_id].insert(attack_key); + self.player_attack_list[player_id].insert(attack_key); + self.target_attack_list[target_id].insert(attack_key); + } + + /// Get all attacks involving a specific player (as attacker or target) + /// + /// Returns a list of (attacker_id, target_id, troops, start_turn, is_outgoing) + /// sorted by start_turn descending (most recent first) + pub fn get_attacks_for_player(&self, player_id: usize) -> Vec<(usize, Option, f32, u64, bool)> { + let mut attacks = Vec::new(); + + // Add outgoing attacks (player is attacker) + for &attack_key in &self.player_attack_list[player_id] { + let attack = &self.attacks[attack_key]; + attacks.push(( + attack.player_id, + attack.target_id, + attack.get_troops(), + attack.get_start_turn(), + true, // outgoing + )); + } + + // Add incoming attacks (player is target) + for &attack_key in &self.target_attack_list[player_id] { + let attack = &self.attacks[attack_key]; + attacks.push(( + attack.player_id, + attack.target_id, + attack.get_troops(), + attack.get_start_turn(), + false, // incoming + )); + } + + // Sort by start_turn descending (most recent first) + attacks.sort_by(|a, b| b.3.cmp(&a.3)); + attacks + } + + /// Remove an attack and update all indices + /// + /// With SlotMap, keys remain stable so no index shifting is needed. + /// HashSet provides O(1) removal without element shifting. + fn remove_attack(&mut self, attack_key: AttackKey) { + let attack = &self.attacks[attack_key]; + let player_id = attack.player_id; + let target_id = attack.target_id; + + // Remove from player attack list (O(1)) + self.player_attack_list[player_id].remove(&attack_key); + + if let Some(target_id) = target_id { + // Remove from target attack list (O(1)) + self.target_attack_list[target_id].remove(&attack_key); + + // Remove from player index (O(1)) + self.player_index[player_id][target_id].remove(&attack_key); + } else { + // Remove from unclaimed index + self.unclaimed_index[player_id] = None; + } + + // Remove attack from slot map - no index shifting needed! + self.attacks.remove(attack_key); + } +} diff --git a/crates/borders-core/src/game/combat/calculator.rs b/crates/borders-core/src/game/combat/calculator.rs new file mode 100644 index 0000000..47dde77 --- /dev/null +++ b/crates/borders-core/src/game/combat/calculator.rs @@ -0,0 +1,152 @@ +/// Pure combat calculation functions +/// +/// This module contains all combat mathematics extracted from the attack system. +/// All functions are pure (no side effects) and deterministic, making them +/// easy to test, reason about, and modify. +use crate::game::constants::combat::*; +use crate::game::player::Player; +use crate::game::territory_manager::TerritoryManager; + +/// Parameters for combat result calculation +pub struct CombatParams<'a> { + pub attacker: &'a Player, + pub defender: Option<&'a Player>, + pub attacker_troops: f32, + pub tile: usize, + pub territory_manager: &'a TerritoryManager, + pub width: u32, +} + +/// Result of combat calculations for conquering one tile +#[derive(Debug, Clone, Copy)] +pub struct CombatResult { + /// Troops lost by the attacker + pub attacker_loss: f32, + /// Troops lost by the defender + pub defender_loss: f32, + /// How much of the "tiles per tick" budget this conquest consumes + pub tiles_per_tick_used: f32, +} + +/// Sigmoid function for smooth scaling curves +/// +/// Used for empire size balancing to create smooth transitions +/// rather than hard thresholds. +pub fn sigmoid(x: f32, decay_rate: f32, midpoint: f32) -> f32 { + 1.0 / (1.0 + (-(x - midpoint) * decay_rate).exp()) +} + +/// Clamp a value between min and max +pub fn clamp(value: f32, min: f32, max: f32) -> f32 { + value.max(min).min(max) +} + +/// Calculate combat result for conquering one tile +/// +/// This function determines troop losses and conquest cost based on: +/// - Attacker and defender troop counts and empire sizes +/// - Terrain properties (currently plains baseline) +/// - Empire size balancing (prevents snowballing) +/// - Defense structures (placeholder for future implementation) +pub fn calculate_combat_result(params: CombatParams) -> CombatResult { + if let Some(defender) = params.defender { + // Attacking claimed territory + let attacker = params.attacker; + + // Base terrain values (plains baseline) + let mut mag = BASE_MAG_PLAINS; + let mut speed = BASE_SPEED_PLAINS; + + // Defense post check (placeholder - always false for now) + let has_defense_post = check_defense_post_nearby(params.tile, defender, params.territory_manager, params.width); + if has_defense_post { + mag *= DEFENSE_POST_MAG_MULTIPLIER; + speed *= DEFENSE_POST_SPEED_MULTIPLIER; + } + + // Empire size balancing - prevents snowballing + // Large defenders get debuffed, large attackers get penalized + let defense_sig = 1.0 - sigmoid(defender.get_territory_size() as f32, DEFENSE_DEBUFF_DECAY_RATE, DEFENSE_DEBUFF_MIDPOINT); + let large_defender_speed_debuff = 0.7 + 0.3 * defense_sig; + let large_defender_attack_debuff = 0.7 + 0.3 * defense_sig; + + let large_attacker_bonus = if attacker.get_territory_size() > LARGE_EMPIRE_THRESHOLD { (LARGE_EMPIRE_THRESHOLD as f32 / attacker.get_territory_size() as f32).sqrt().powf(0.7) } else { 1.0 }; + + let large_attacker_speed_bonus = if attacker.get_territory_size() > LARGE_EMPIRE_THRESHOLD { (LARGE_EMPIRE_THRESHOLD as f32 / attacker.get_territory_size() as f32).powf(0.6) } else { 1.0 }; + + // Calculate troop ratio + let troop_ratio = clamp(defender.get_troops() / params.attacker_troops.max(1.0), 0.6, 2.0); + + // Final attacker loss + let attacker_loss = troop_ratio * mag * 0.8 * large_defender_attack_debuff * large_attacker_bonus; + + // Defender loss (simple: troops per tile) + let defender_loss = defender.get_troops() / defender.get_territory_size().max(1) as f32; + + // Tiles per tick cost for this tile + let tiles_per_tick_used = clamp(defender.get_troops() / (5.0 * params.attacker_troops.max(1.0)), 0.2, 1.5) * speed * large_defender_speed_debuff * large_attacker_speed_bonus; + + CombatResult { attacker_loss, defender_loss, tiles_per_tick_used } + } else { + // Attacking unclaimed territory + CombatResult { + attacker_loss: BASE_MAG_PLAINS / 5.0, // 16.0 for plains + defender_loss: 0.0, + tiles_per_tick_used: clamp((2000.0 * BASE_SPEED_PLAINS.max(10.0)) / params.attacker_troops.max(1.0), 5.0, 100.0), + } + } +} + +/// Calculate tiles conquered per tick based on troop ratio and border size +/// +/// This determines how fast an attack progresses. It's based on: +/// - The attacker's troop advantage (or disadvantage) +/// - The size of the attack border +/// - Random variation for organic-looking expansion +pub fn calculate_tiles_per_tick(attacker_troops: f32, defender: Option<&Player>, border_size: f32) -> f32 { + if let Some(defender) = defender { + // Dynamic based on troop ratio + let ratio = ((5.0 * attacker_troops) / defender.get_troops().max(1.0)) * 2.0; + let clamped_ratio = clamp(ratio, 0.01, 0.5); + clamped_ratio * border_size * CLAIMED_TILES_PER_TICK_MULTIPLIER + } else { + // Fixed rate for unclaimed territory + border_size * UNCLAIMED_TILES_PER_TICK_MULTIPLIER + } +} + +/// Check if defender has a defense post nearby (placeholder) +/// +/// This will be implemented when defense structures are added to the game. +/// For now, always returns false. +fn check_defense_post_nearby(_tile: usize, _defender: &Player, _territory_manager: &TerritoryManager, _width: u32) -> bool { + // Placeholder for future defense post implementation + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sigmoid_midpoint() { + let result = sigmoid(DEFENSE_DEBUFF_MIDPOINT, DEFENSE_DEBUFF_DECAY_RATE, DEFENSE_DEBUFF_MIDPOINT); + assert!((result - 0.5).abs() < 0.01, "Sigmoid should be ~0.5 at midpoint"); + } + + #[test] + fn test_clamp() { + assert_eq!(clamp(5.0, 0.0, 10.0), 5.0); + assert_eq!(clamp(-1.0, 0.0, 10.0), 0.0); + assert_eq!(clamp(15.0, 0.0, 10.0), 10.0); + } + + #[test] + fn test_unclaimed_attack_fixed_losses() { + // Unclaimed territory should have fixed attacker loss + let result = CombatResult { attacker_loss: BASE_MAG_PLAINS / 5.0, defender_loss: 0.0, tiles_per_tick_used: 10.0 }; + + assert_eq!(result.attacker_loss, 16.0); + assert_eq!(result.defender_loss, 0.0); + } +} diff --git a/crates/borders-core/src/game/combat/executor.rs b/crates/borders-core/src/game/combat/executor.rs new file mode 100644 index 0000000..b00495c --- /dev/null +++ b/crates/borders-core/src/game/combat/executor.rs @@ -0,0 +1,333 @@ +/// Attack execution logic +/// +/// This module contains the `AttackExecutor` which manages the progression +/// of a single attack over multiple turns. It handles tile prioritization, +/// border expansion, and conquest mechanics. +use std::collections::{BinaryHeap, HashSet}; + +use rand::Rng; + +use super::calculator::{CombatParams, calculate_combat_result, calculate_tiles_per_tick}; +use crate::game::constants::combat::*; +use crate::game::rng::DeterministicRng; +use crate::game::utils::for_each_neighbor; +use crate::game::{TileOwnership, borders::BorderManager, player::Player, territory_manager::TerritoryManager}; + +/// Priority queue entry for tile conquest +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct TilePriority { + tile: usize, + priority: i64, // Lower value = higher priority (conquered sooner) +} + +impl PartialOrd for TilePriority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TilePriority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other.priority.cmp(&self.priority).then_with(|| self.tile.cmp(&other.tile)) + } +} + +/// Configuration for creating an AttackExecutor +pub struct AttackConfig<'a> { + pub player_id: usize, + pub target_id: Option, + pub troops: f32, + pub target_tile: usize, + pub border_tiles: Option<&'a HashSet>, + pub territory_manager: &'a TerritoryManager, + pub border_manager: &'a BorderManager, + pub width: u32, + pub turn_number: u64, +} + +/// Executes a single ongoing attack (conquering tiles over time) +/// +/// An attack progresses over multiple turns, conquering tiles based on: +/// - Available troops +/// - Troop ratio vs defender +/// - Border size and connectivity +/// - Combat formulas from the calculator module +/// +/// The executor maintains a priority queue of tiles to conquer and updates +/// borders as it progresses. +pub struct AttackExecutor { + pub player_id: usize, + pub target_id: Option, + troops: f32, + border_tiles: HashSet, + priority_queue: BinaryHeap, + start_turn: u64, + current_turn: u64, + tiles_conquered: u64, // Counter for each tile conquered (for priority calculation) +} + +impl AttackExecutor { + /// Create a new attack executor + pub fn new(config: AttackConfig, rng: &DeterministicRng) -> Self { + let mut executor = Self { player_id: config.player_id, target_id: config.target_id, troops: config.troops, border_tiles: HashSet::new(), priority_queue: BinaryHeap::new(), start_turn: config.turn_number, current_turn: config.turn_number, tiles_conquered: 0 }; + + executor.initialize_border(config.border_tiles, config.territory_manager, config.border_manager, config.width, rng); + + executor + } + + /// Modify the amount of troops in the attack + pub fn modify_troops(&mut self, amount: f32) { + self.troops += amount; + } + + /// Add new border tiles to the attack, allowing expansion from multiple fronts + /// + /// This enables multi-region expansion when attacking the same target from different areas + pub fn add_borders(&mut self, new_border_tiles: &HashSet, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) { + // Add neighbors from each new border tile + let height = territory_manager.len() as u32 / width; + for &tile in new_border_tiles { + for_each_neighbor(tile, width, height, |neighbor| { + let neighbor_is_target = if let Some(target_id) = self.target_id { territory_manager.is_owner(neighbor, target_id) } else { !territory_manager.has_owner(neighbor) && !territory_manager.is_water(neighbor) }; + if neighbor_is_target && !self.border_tiles.contains(&neighbor) { + self.border_tiles.insert(neighbor); + let priority = self.calculate_tile_priority(neighbor, territory_manager, width, rng); + self.priority_queue.push(TilePriority { tile: neighbor, priority }); + } + }); + } + } + + /// Oppose an attack (counter-attack) + /// + /// Returns true if the attack continues, false if it was defeated + pub fn oppose(&mut self, troop_count: f32) -> bool { + if self.troops > troop_count { + self.troops -= troop_count; + true + } else { + false + } + } + + /// Get the amount of troops in the attack + pub fn get_troops(&self) -> f32 { + self.troops.max(0.0).floor() + } + + /// Get the turn this attack started + pub fn get_start_turn(&self) -> u64 { + self.start_turn + } + + /// Tick the attack executor + /// + /// Returns true if the attack continues, false if it's finished + pub fn tick(&mut self, players: &mut [Player], territory_manager: &mut TerritoryManager, border_manager: &mut BorderManager, width: u32, rng: &DeterministicRng) -> bool { + let _ = tracing::debug_span!("attack_tick"); + let _span = tracing::trace_span!("attack_tick", player_id = self.player_id).entered(); + + self.current_turn += 1; + + // Calculate how many tiles to conquer this tick + let mut tiles_per_tick = self.calculate_tiles_per_tick(players, rng); + + // Process tiles from priority queue + while tiles_per_tick > 0.0 { + if self.troops < 1.0 { + return false; + } + + if self.priority_queue.is_empty() { + // Refresh border tiles one last time before giving up + self.refresh_border(border_manager, territory_manager, width, rng); + + // If still empty after refresh, attack is finished + if self.priority_queue.is_empty() { + return false; + } + } + + let tile_priority = self.priority_queue.pop().unwrap(); + let tile = tile_priority.tile; + self.border_tiles.remove(&tile); + + // Check connectivity first + let mut on_border = false; + let height = territory_manager.len() as u32 / width; + for_each_neighbor(tile, width, height, |neighbor| { + if territory_manager.is_owner(neighbor, self.player_id) { + on_border = true; + } + }); + + // Check if tile is still valid target + let tile_valid = if let Some(target_id) = self.target_id { territory_manager.is_owner(tile, target_id) } else { !territory_manager.has_owner(tile) && !territory_manager.is_water(tile) }; + + // Skip if either check fails + if !tile_valid || !on_border { + continue; + } + + // Add neighbors BEFORE conquering (critical for correct expansion) + self.add_neighbors_to_border(tile, territory_manager, width, rng); + + // Calculate losses for this tile + let combat_result = calculate_combat_result(CombatParams { attacker: &players[self.player_id], defender: self.target_id.map(|id| &players[id]), attacker_troops: self.troops, tile, territory_manager, width }); + + // Check if we still have enough troops to conquer this tile + if self.troops < combat_result.attacker_loss { + return false; + } + + // Apply troop losses + self.troops -= combat_result.attacker_loss; + if let Some(target_id) = self.target_id { + players[target_id].remove_troops(combat_result.defender_loss); + } + + // Conquer the tile + let previous_owner = territory_manager.conquer(tile, self.player_id); + + // Update player territory sizes + let previous_ownership = TileOwnership::from_u16(previous_owner); + if let Some(nation_id) = previous_ownership.nation_id() { + players[nation_id as usize].remove_tile(tile); + } + players[self.player_id].add_tile(tile); + + // Increment tiles conquered counter (used for priority calculation) + self.tiles_conquered += 1; + + // Decrement tiles per tick counter + tiles_per_tick -= combat_result.tiles_per_tick_used; + } + + // Check if attack should continue + !self.priority_queue.is_empty() && self.troops >= 1.0 + } + + /// Calculate tiles conquered per tick based on troop ratio and border size + fn calculate_tiles_per_tick(&mut self, players: &[Player], rng: &DeterministicRng) -> f32 { + // Add random 0-4 to border size + // This introduces natural variation in expansion speed + let mut context_rng = rng.for_context(self.player_id as u64); + let random_border_adjustment = context_rng.random_range(0..BORDER_RANDOM_ADJUSTMENT_MAX) as f32; + let border_size = self.border_tiles.len() as f32 + random_border_adjustment; + + let defender = self.target_id.map(|id| &players[id]); + calculate_tiles_per_tick(self.troops, defender, border_size) + } + + /// Initialize border tiles from player's existing borders + fn initialize_border(&mut self, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, border_manager: &BorderManager, width: u32, rng: &DeterministicRng) { + let borders = border_tiles.unwrap_or_else(|| border_manager.get_border_tiles(self.player_id)); + + // Find all target tiles adjacent to our borders + let height = territory_manager.len() as u32 / width; + for &tile in borders { + for_each_neighbor(tile, width, height, |neighbor| { + let neighbor_is_target = if let Some(target_id) = self.target_id { territory_manager.is_owner(neighbor, target_id) } else { !territory_manager.has_owner(neighbor) && !territory_manager.is_water(neighbor) }; + if neighbor_is_target && !self.border_tiles.contains(&neighbor) { + self.border_tiles.insert(neighbor); + let priority = self.calculate_tile_priority(neighbor, territory_manager, width, rng); + self.priority_queue.push(TilePriority { tile: neighbor, priority }); + } + }); + } + } + + /// Refresh the attack border by re-scanning all player border tiles + /// + /// This gives the attack one last chance to find conquerable tiles before ending + fn refresh_border(&mut self, border_manager: &BorderManager, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) { + // Clear existing border and queue + self.priority_queue.clear(); + self.border_tiles.clear(); + + // Re-scan all player border tiles to find new targets + let player_borders = border_manager.get_border_tiles(self.player_id); + let height = territory_manager.len() as u32 / width; + for &tile in player_borders { + for_each_neighbor(tile, width, height, |neighbor| { + let neighbor_is_target = if let Some(target_id) = self.target_id { territory_manager.is_owner(neighbor, target_id) } else { !territory_manager.has_owner(neighbor) && !territory_manager.is_water(neighbor) }; + if neighbor_is_target && !self.border_tiles.contains(&neighbor) { + self.border_tiles.insert(neighbor); + let priority = self.calculate_tile_priority(neighbor, territory_manager, width, rng); + self.priority_queue.push(TilePriority { tile: neighbor, priority }); + } + }); + } + } + + /// Add neighbors of a newly conquered tile to the border + fn add_neighbors_to_border(&mut self, tile: usize, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) { + let height = territory_manager.len() as u32 / width; + for_each_neighbor(tile, width, height, |neighbor| { + let neighbor_is_target = if let Some(target_id) = self.target_id { territory_manager.is_owner(neighbor, target_id) } else { !territory_manager.has_owner(neighbor) && !territory_manager.is_water(neighbor) }; + if neighbor_is_target && !self.border_tiles.contains(&neighbor) { + self.border_tiles.insert(neighbor); + let priority = self.calculate_tile_priority(neighbor, territory_manager, width, rng); + self.priority_queue.push(TilePriority { tile: neighbor, priority }); + } + }); + } + + /// Calculate priority for a tile (lower = conquered sooner) + /// + /// Uses tiles_conquered counter to ensure wave-like expansion + fn calculate_tile_priority(&self, tile: usize, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) -> i64 { + // Count how many neighbors are owned by attacker + let mut num_owned_by_attacker = 0; + let height = territory_manager.len() as u32 / width; + for_each_neighbor(tile, width, height, |neighbor| { + if territory_manager.is_owner(neighbor, self.player_id) { + num_owned_by_attacker += 1; + } + }); + + // Terrain magnitude (placeholder - always 1.0 for plains) + let terrain_mag = 1.0; + + // Random factor (0-7) + let mut tile_rng = rng.for_tile(tile); + let random_factor = tile_rng.random_range(0..TILE_PRIORITY_RANDOM_MAX); + + // Priority calculation (lower = higher priority, conquered sooner) + // Base calculation: tiles surrounded by more attacker neighbors get LOWER modifier values + // Adding tiles_conquered ensures tiles discovered earlier get lower priority values + // This creates wave-like expansion: older tiles (lower priority) conquered before newer tiles (higher priority) + let base = (random_factor + 10) as f32; + let modifier = 1.0 - (num_owned_by_attacker as f32 * 0.5) + (terrain_mag / 2.0); + (base * modifier) as i64 + self.tiles_conquered as i64 + } + + /// Handle the addition of a tile to the player's territory + pub fn handle_player_tile_add(&mut self, tile: usize, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) { + // When player gains a tile, check its neighbors for new targets + self.add_neighbors_to_border(tile, territory_manager, width, rng); + } + + /// Handle the addition of a tile to the target's territory + pub fn handle_target_tile_add(&mut self, tile: usize, territory_manager: &TerritoryManager, width: u32, rng: &DeterministicRng) { + // If target gains a tile that borders our territory, add it to attack + if Self::check_borders_tile(tile, self.player_id, territory_manager, width) && !self.border_tiles.contains(&tile) { + self.border_tiles.insert(tile); + let priority = self.calculate_tile_priority(tile, territory_manager, width, rng); + self.priority_queue.push(TilePriority { tile, priority }); + } + } + + /// Check if a tile borders the player's territory + fn check_borders_tile(tile: usize, player_id: usize, territory_manager: &TerritoryManager, width: u32) -> bool { + let height = territory_manager.len() as u32 / width; + let mut has_border = false; + for_each_neighbor(tile, width, height, |neighbor| { + if territory_manager.is_owner(neighbor, player_id) { + has_border = true; + } + }); + has_border + } +} diff --git a/crates/borders-core/src/game/combat/mod.rs b/crates/borders-core/src/game/combat/mod.rs new file mode 100644 index 0000000..1bf65f9 --- /dev/null +++ b/crates/borders-core/src/game/combat/mod.rs @@ -0,0 +1,7 @@ +pub mod active; +pub mod calculator; +pub mod executor; + +pub use active::*; +pub use calculator::*; +pub use executor::*; diff --git a/crates/borders-core/src/game/connectivity.rs b/crates/borders-core/src/game/connectivity.rs new file mode 100644 index 0000000..d372d35 --- /dev/null +++ b/crates/borders-core/src/game/connectivity.rs @@ -0,0 +1,155 @@ +use crate::game::TileOwnership; +use crate::game::terrain::TerrainData; +use std::collections::VecDeque; + +/// Check if a target tile's region connects to any of the player's tiles +/// Uses flood-fill through tiles matching the target's ownership +/// Returns true if connected (normal attack), false if disconnected (ship needed) +pub fn is_connected_to_player(territory: &[u16], target_tile: usize, player_id: u16, width: usize, height: usize) -> bool { + let target_ownership = TileOwnership::from_u16(territory[target_tile]); + + // Can't connect to water + if target_ownership.is_water() { + return false; + } + + // If target is owned by player, it's already connected + if target_ownership.is_owned_by(player_id) { + return true; + } + + // Flood-fill from target through tiles with same ownership + let mut queue = VecDeque::new(); + let mut visited = vec![false; width * height]; + + queue.push_back(target_tile); + visited[target_tile] = true; + + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; // Cardinal directions only + + while let Some(current) = queue.pop_front() { + let x = current % width; + let y = current / width; + + // Check all 4 neighbors + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + // Check bounds + if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 { + continue; + } + + let neighbor_idx = nx as usize + ny as usize * width; + + // Skip if already visited + if visited[neighbor_idx] { + continue; + } + + let neighbor_ownership = TileOwnership::from_u16(territory[neighbor_idx]); + + // Check if we found a player tile - SUCCESS! + if neighbor_ownership.is_owned_by(player_id) { + return true; + } + + // Only continue through tiles matching target's ownership + if neighbor_ownership == target_ownership { + visited[neighbor_idx] = true; + queue.push_back(neighbor_idx); + } + } + } + + // Exhausted search without finding player tile + false +} + +/// Find the nearest coastal tile in a region by flood-filling from target +/// Only expands through tiles matching the target's ownership +/// Returns coastal tile index if found +pub fn find_coastal_tile_in_region(territory: &[u16], terrain: &TerrainData, target_tile: usize, width: usize, height: usize) -> Option { + let target_ownership = TileOwnership::from_u16(territory[target_tile]); + + // Can't find coastal tile in water + if target_ownership.is_water() { + return None; + } + + // Check if target itself is coastal + if is_coastal_tile(terrain, target_tile, width, height) { + return Some(target_tile); + } + + // BFS from target through same-ownership tiles + let mut queue = VecDeque::new(); + let mut visited = vec![false; width * height]; + + queue.push_back(target_tile); + visited[target_tile] = true; + + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + while let Some(current) = queue.pop_front() { + let x = current % width; + let y = current / width; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 { + continue; + } + + let neighbor_idx = nx as usize + ny as usize * width; + + if visited[neighbor_idx] { + continue; + } + + let neighbor_ownership = TileOwnership::from_u16(territory[neighbor_idx]); + + // Only expand through matching ownership + if neighbor_ownership == target_ownership { + visited[neighbor_idx] = true; + + // Check if this tile is coastal + if is_coastal_tile(terrain, neighbor_idx, width, height) { + return Some(neighbor_idx); + } + + queue.push_back(neighbor_idx); + } + } + } + + None +} + +/// Check if a tile is coastal (land tile adjacent to water) +fn is_coastal_tile(terrain: &TerrainData, tile: usize, width: usize, height: usize) -> bool { + let x = tile % width; + let y = tile / width; + + // Must be land tile + if terrain.is_navigable((x as u32, y as u32)) { + return false; + } + + // Check if any neighbor is water (4-directional) + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && nx < width as i32 && ny < height as i32 && terrain.is_navigable((nx as u32, ny as u32)) { + return true; + } + } + + false +} diff --git a/crates/borders-core/src/game/constants.rs b/crates/borders-core/src/game/constants.rs new file mode 100644 index 0000000..5a4d4ef --- /dev/null +++ b/crates/borders-core/src/game/constants.rs @@ -0,0 +1,49 @@ +/// Game constants organized by domain +/// +/// This module centralizes all game balance constants that were previously +/// scattered across multiple files. Constants are grouped by gameplay domain +/// for easy discovery and tuning. +pub mod combat { + /// Empire size balancing - prevents snowballing by large empires + /// Defense effectiveness decreases as empire grows beyond this threshold + pub const DEFENSE_DEBUFF_MIDPOINT: f32 = 150_000.0; + + /// Rate of defense effectiveness decay for large empires + /// Uses natural log decay for smooth scaling + pub const DEFENSE_DEBUFF_DECAY_RATE: f32 = std::f32::consts::LN_2 / 50_000.0; + + /// Base terrain magnitude cost for plains (baseline terrain) + /// Determines troop losses when conquering a tile + pub const BASE_MAG_PLAINS: f32 = 80.0; + + /// Base terrain speed for plains (baseline terrain) + /// Affects how many tiles can be conquered per tick + pub const BASE_SPEED_PLAINS: f32 = 16.5; + + /// Maximum random adjustment to border size when calculating expansion speed + /// Introduces natural variation in attack progression (0-4 range) + pub const BORDER_RANDOM_ADJUSTMENT_MAX: u32 = 5; + + /// Multiplier for tiles conquered per tick when attacking unclaimed territory + pub const UNCLAIMED_TILES_PER_TICK_MULTIPLIER: f32 = 2.0; + + /// Multiplier for tiles conquered per tick when attacking claimed territory + pub const CLAIMED_TILES_PER_TICK_MULTIPLIER: f32 = 3.0; + + /// Large empire threshold for attack penalties (>100k tiles) + pub const LARGE_EMPIRE_THRESHOLD: usize = 100_000; + + /// Random factor range for tile priority calculation (0-7) + pub const TILE_PRIORITY_RANDOM_MAX: u32 = 8; + + /// Defense post magnitude multiplier (when implemented) + pub const DEFENSE_POST_MAG_MULTIPLIER: f32 = 5.0; + + /// Defense post speed multiplier (when implemented) + pub const DEFENSE_POST_SPEED_MULTIPLIER: f32 = 3.0; +} + +pub mod spawning { + /// Radius of tiles claimed around spawn point (creates 5x5 square) + pub const SPAWN_RADIUS: i32 = 2; +} diff --git a/crates/borders-core/src/game/game_instance.rs b/crates/borders-core/src/game/game_instance.rs new file mode 100644 index 0000000..958213f --- /dev/null +++ b/crates/borders-core/src/game/game_instance.rs @@ -0,0 +1,420 @@ +use crate::game::action::GameAction; +use crate::game::borders::BorderManager; +use crate::game::bot::BotManager; +use crate::game::combat::ActiveAttacks; +use crate::game::player_manager::PlayerManager; +use crate::game::rng::DeterministicRng; +use crate::game::ships::ShipManager; +use crate::game::terrain::TerrainData; +use crate::game::territory_manager::TerritoryManager; +use crate::networking::{Intent, Turn}; +use bevy_ecs::prelude::*; +use std::collections::HashSet; +use std::sync::Arc; + +/// Troop count specification for attacks +enum TroopCount { + /// Use a ratio of the player's current troops (0.0-1.0) + Ratio(f32), + /// Use an absolute troop count + Absolute(u32), +} + +/// Game state resource - DETERMINISTIC, SHARED across ALL clients +/// +/// **Important: This is GLOBAL/SHARED state that must be identical on all clients!** +/// +/// This resource contains the authoritative game state that: +/// - Is identical across all clients (server, players, spectators) +/// - Processes turns deterministically (same input → same output) +/// - Is used for hash validation and network synchronization +/// - Continues running even when individual players are eliminated +/// +/// What belongs here: +/// - Territory ownership, player stats, attacks, resources +/// - Turn number, RNG seed (for determinism) +/// - Any state that affects gameplay or must be validated +/// +/// What does NOT belong here: +/// - Client-specific UI state (use LocalPlayerContext) +/// - Individual player outcomes like Victory/Defeat (use LocalPlayerContext) +/// - Rendering preferences, camera position, etc. (use local resources) +/// +/// The game never "stops" based on a single player's outcome - it continues +/// until a global end condition is met (e.g., all players eliminated, turn limit). +#[derive(Resource)] +pub struct GameInstance { + pub player_manager: PlayerManager, + pub territory_manager: TerritoryManager, + pub active_attacks: ActiveAttacks, + pub border_manager: BorderManager, + pub bot_manager: BotManager, + pub ship_manager: ShipManager, + pub terrain: Arc, + pub rng: DeterministicRng, + pub turn_number: u64, + /// Cached set of all coastal tile indices (precomputed once, never changes) + /// A coastal tile is a land tile adjacent to water + pub coastal_tiles: HashSet, +} + +impl GameInstance { + #[allow(clippy::too_many_arguments)] + pub fn new(player_manager: PlayerManager, territory_manager: TerritoryManager, active_attacks: ActiveAttacks, border_manager: BorderManager, bot_manager: BotManager, ship_manager: ShipManager, terrain: Arc, rng_seed: u64) -> Self { + // Precompute coastal tiles (land tiles adjacent to water) + let width = territory_manager.width() as usize; + let height = territory_manager.height() as usize; + let coastal_tiles = Self::compute_coastal_tiles(&terrain, width, height); + + Self { player_manager, territory_manager, active_attacks, border_manager, bot_manager, ship_manager, terrain, rng: DeterministicRng::new(rng_seed), turn_number: 0, coastal_tiles } + } + + /// Compute all coastal tile indices (land tiles adjacent to water) + /// This is called once at initialization and cached + fn compute_coastal_tiles(terrain: &TerrainData, width: usize, height: usize) -> HashSet { + let mut coastal_tiles = HashSet::new(); + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for y in 0..height { + for x in 0..width { + let tile_idx = x + y * width; + + // Skip water tiles + if terrain.is_navigable((x as u32, y as u32)) { + continue; + } + + // Check if any neighbor is water (4-directional) + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && nx < width as i32 && ny < height as i32 && terrain.is_navigable((nx as u32, ny as u32)) { + coastal_tiles.insert(tile_idx); + break; + } + } + } + } + + coastal_tiles + } + + pub fn execute_turn(&mut self, turn: &Turn) { + let _span = tracing::trace_span!("execute_turn", turn_number = self.turn_number, intent_count = turn.intents.len()).entered(); + + // Update RNG for this turn + self.rng.update_turn(self.turn_number); + + // PHASE 1: Process bot actions (deterministic, based on turn N-1 state) + let bot_player_ids = self.bot_manager.bot_player_ids().to_vec(); + for (bot_index, &player_id) in bot_player_ids.iter().enumerate() { + if let Some(player) = self.player_manager.get_player(player_id) { + if !player.is_alive() { + continue; + } + + if let Some(action) = self.bot_manager.calculate_action(bot_index, self.turn_number, player, &self.territory_manager, &self.border_manager, self.rng.turn_number()) { + tracing::trace!(bot_index, player_id, "Bot action executed"); + self.apply_action(action); + } + } + } + + // PHASE 2: Process player intents (from network) + for intent in &turn.intents { + match intent { + Intent::Action(action) => { + self.apply_action(action.clone()); + } + Intent::SetSpawn { .. } => { + // SetSpawn intents should not appear in Turn messages + // They are only valid during spawn phase and handled separately + // If we see one here, it's likely a bug or late arrival - ignore it + } + } + } + + // PHASE 3: Update ships and process arrivals + let ship_arrivals = self.ship_manager.update_ships(); + for (owner_id, target_tile, troops) in ship_arrivals { + tracing::debug!(owner_id, target_tile, troops, "Ship arrived at destination, establishing beachhead"); + + let player_id = owner_id as usize; + let target_tile_usize = target_tile; + + // Step 1: Force-claim the landing tile as beachhead + let previous_owner = self.territory_manager.conquer(target_tile_usize, player_id); + + // Border updates now handled by update_player_borders_system (batched at end of turn) + + // Step 2: Update player stats + let previous_ownership = crate::game::TileOwnership::from_u16(previous_owner); + if let Some(nation_id) = previous_ownership.nation_id() + && let Some(prev_owner) = self.player_manager.get_player_mut(nation_id as usize) + { + prev_owner.remove_tile(target_tile_usize); + } + if let Some(player) = self.player_manager.get_player_mut(player_id) { + player.add_tile(target_tile_usize); + } + + // Step 4: Notify active attacks of territory change + self.active_attacks.handle_territory_add(target_tile_usize, player_id, &self.territory_manager, &self.rng); + + // Step 5: Create attack from beachhead to expand + // Note: Do NOT add troops back - they were deducted at ship launch + if let Some(player) = self.player_manager.get_player_mut(player_id) { + let _ = player; + + // Find tiles adjacent to the beachhead + let width = self.territory_manager.width() as usize; + let height = self.territory_manager.height() as usize; + let beachhead_x = target_tile_usize % width; + let beachhead_y = target_tile_usize / width; + + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + let mut adjacent_tiles = Vec::new(); + + for (dx, dy) in directions { + let nx = beachhead_x as i32 + dx; + let ny = beachhead_y as i32 + dy; + + if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height { + let neighbor_idx = nx as usize + ny as usize * width; + adjacent_tiles.push(neighbor_idx); + } + } + + // Find valid attack targets (not water, not our own tiles) + let valid_targets: Vec = adjacent_tiles.iter().filter(|&&tile| !self.territory_manager.is_water(tile) && self.territory_manager.get_owner(tile) as usize != player_id).copied().collect(); + + // Pick a deterministic random target from valid targets + if !valid_targets.is_empty() { + // Deterministic random selection using turn number and beachhead position + let seed = self.turn_number.wrapping_mul(31).wrapping_add(target_tile_usize as u64); + let index = (seed % valid_targets.len() as u64) as usize; + let attack_target = valid_targets[index]; + + // Create a beachhead border set containing just this tile for the attack + let beachhead_borders: HashSet = std::iter::once(target_tile_usize).collect(); + + // Use the ship's troops for the attack (already deducted at launch) + self.handle_attack_internal( + owner_id, + attack_target as u32, + TroopCount::Absolute(troops), + false, // Don't deduct - troops were already deducted at ship launch + Some(&beachhead_borders), + ); + } else { + tracing::debug!(player_id, target_tile_usize, "Ship landed but no valid attack targets found (all adjacent tiles are water or owned)"); + } + } + } + + // PHASE 4: Tick game systems (attacks, etc.) + // Note: Income is processed by process_player_income_system at 10 TPS (before this executes) + + self.active_attacks.tick(self.player_manager.get_players_mut(), &mut self.territory_manager, &mut self.border_manager, &self.rng); + + self.turn_number += 1; + } + + /// Apply a game action (used by both bots and player intents) + pub fn apply_action(&mut self, action: GameAction) { + match action { + GameAction::Spawn { player_id, tile_index } => { + self.handle_spawn(player_id, tile_index); + } + GameAction::Attack { player_id, target_tile, troops_ratio } => { + self.handle_attack(player_id, target_tile, troops_ratio); + } + GameAction::LaunchShip { player_id, target_tile, troops_percent } => { + self.handle_launch_ship(player_id, target_tile, troops_percent); + } + } + } + + pub fn handle_spawn(&mut self, player_id: u16, tile_index: u32) { + let player_id = player_id as usize; + let width = self.territory_manager.width(); + let height = self.territory_manager.height(); + let tile = tile_index as usize; + + if self.territory_manager.has_owner(tile) || self.territory_manager.is_water(tile) { + tracing::debug!(player_id, tile, "Spawn on occupied/water tile ignored"); + return; + } + + let spawn_x = tile as u32 % width; + let spawn_y = tile as u32 / width; + + let mut changed: HashSet = HashSet::new(); + for dy in -2..=2 { + for dx in -2..=2 { + let x = (spawn_x as i32 + dx).clamp(0, width as i32 - 1) as u32; + let y = (spawn_y as i32 + dy).clamp(0, height as i32 - 1) as u32; + let idx = (x + y * width) as usize; + if !self.territory_manager.has_owner(idx) && !self.territory_manager.is_water(idx) { + self.territory_manager.conquer(idx, player_id); + changed.insert(idx); + } + } + } + + if !changed.is_empty() { + // Border updates now handled by update_player_borders_system (batched at end of turn) + + // Update player stats + if let Some(player) = self.player_manager.get_player_mut(player_id) { + for &t in &changed { + player.add_tile(t); + } + } + // Notify active attacks that territory changed + for &t in &changed { + self.active_attacks.handle_territory_add(t, player_id, &self.territory_manager, &self.rng); + } + } + } + + pub fn handle_attack(&mut self, player_id: u16, target_tile: u32, troops_ratio: f32) { + self.handle_attack_internal(player_id, target_tile, TroopCount::Ratio(troops_ratio), true, None); + } + + /// Handle attack with specific border tiles and troop allocation + fn handle_attack_internal(&mut self, player_id: u16, target_tile: u32, troop_count: TroopCount, deduct_from_player: bool, border_tiles: Option<&HashSet>) { + let player_id = player_id as usize; + let target_tile = target_tile as usize; + + let target_owner = self.territory_manager.get_owner(target_tile); + + if target_owner as usize == player_id { + tracing::debug!(player_id, target_tile, "Attack on own tile ignored"); + return; // Can't attack self + } + + let troops = match troop_count { + TroopCount::Ratio(ratio) => { + if let Some(player) = self.player_manager.get_player(player_id) { + player.get_troops() * ratio + } else { + return; + } + } + TroopCount::Absolute(count) => count as f32, + }; + + // Deduct troops from the player's pool when creating the attack (if requested) + if deduct_from_player { + if let Some(player) = self.player_manager.get_player_mut(player_id) { + player.remove_troops(troops); + } else { + return; + } + } + + let border_tiles_to_use = border_tiles.or_else(|| Some(self.border_manager.get_border_tiles(player_id))); + + use crate::game::TileOwnership; + if TileOwnership::from_u16(target_owner).is_unclaimed() { + if self.player_manager.get_player(player_id).is_some() { + self.active_attacks.schedule_unclaimed(player_id, troops, target_tile, border_tiles_to_use, &self.territory_manager, &self.border_manager, self.turn_number, &self.rng); + } + } else if self.player_manager.get_player(target_owner as usize).is_some() && self.player_manager.get_player(player_id).is_some() { + self.active_attacks.schedule_attack(player_id, target_owner as usize, target_tile, troops, border_tiles_to_use, &self.territory_manager, &self.border_manager, self.turn_number, &self.rng); + } + } + + pub fn handle_launch_ship(&mut self, player_id: u16, target_tile: u32, troops_percent: u32) { + use crate::game::ships::{SHIP_TROOP_PERCENT, ship_pathfinding}; + + let player_id_usize = player_id as usize; + let target_tile_usize = target_tile as usize; + + // Check if player exists and has troops + let player_troops = if let Some(player) = self.player_manager.get_player(player_id_usize) { + if !player.is_alive() { + tracing::debug!(player_id, "Dead player cannot launch ships"); + return; + } + player.get_troops() + } else { + tracing::debug!(player_id, "Player not found"); + return; + }; + + if player_troops <= 0.0 { + tracing::debug!(player_id, "Player has no troops to launch ship"); + return; + } + + // Calculate troop count: use provided percentage, or default to 20% if 0 + let troops_to_send = if troops_percent > 0 { + // Clamp to reasonable range (1-100%) + let clamped = troops_percent.clamp(1, 100); + let calculated = (player_troops * (clamped as f32 / 100.0)).floor() as u32; + tracing::debug!(player_id, player_troops, troops_percent = clamped, troops_to_send = calculated, "Ship launch troop calculation"); + calculated + } else { + // Default: 20% of troops + let calculated = (player_troops * SHIP_TROOP_PERCENT).floor() as u32; + tracing::debug!(player_id, player_troops, default_percent = SHIP_TROOP_PERCENT, troops_to_send = calculated, "Ship launch troop calculation (default)"); + calculated + }; + if troops_to_send == 0 { + tracing::debug!(player_id, "Not enough troops to launch ship"); + return; + } + + let width = self.territory_manager.width() as usize; + let height = self.territory_manager.height() as usize; + + // Find target's nearest coastal tile + let target_coastal_tile = crate::game::connectivity::find_coastal_tile_in_region(self.territory_manager.as_u16_slice(), &self.terrain, target_tile_usize, width, height); + + let target_coastal_tile = match target_coastal_tile { + Some(tile) => tile, + None => { + tracing::debug!(player_id, target_tile, "No coastal tile found in target region"); + return; + } + }; + + // Find player's nearest coastal tile + let player_border_tiles = self.border_manager.get_border_tiles(player_id_usize); + let launch_tile = ShipManager::find_nearest_player_coastal_tile(&self.coastal_tiles, player_border_tiles, target_coastal_tile, width); + + let launch_tile = match launch_tile { + Some(tile) => tile, + None => { + tracing::debug!(player_id, target_tile, "Player has no coastal tiles to launch from"); + return; + } + }; + + // Calculate water path from launch tile to target coastal tile + let path = ship_pathfinding::find_water_path(&self.terrain, launch_tile, target_coastal_tile, crate::game::ships::SHIP_MAX_PATH_LENGTH); + + let path = match path { + Some(p) => p, // Use full A* path for accurate frontend interpolation + None => { + tracing::debug!(player_id, target_tile, launch_tile, "No water path found"); + return; + } + }; + + // Launch the ship + if self.ship_manager.launch_ship(player_id, troops_to_send, path, self.turn_number).is_some() { + // Deduct troops from player + if let Some(player) = self.player_manager.get_player_mut(player_id_usize) { + player.remove_troops(troops_to_send as f32); + } + + tracing::debug!(player_id, target_tile, troops_to_send, launch_tile, "Ship launched successfully"); + } else { + tracing::debug!(player_id, "Failed to launch ship (likely at ship limit)"); + } + } +} diff --git a/crates/borders-core/src/game/income.rs b/crates/borders-core/src/game/income.rs new file mode 100644 index 0000000..0610118 --- /dev/null +++ b/crates/borders-core/src/game/income.rs @@ -0,0 +1,52 @@ +use bevy_ecs::prelude::*; +use tracing::trace; + +use crate::game::{BorderTiles, BotPlayer, CurrentTurn, GameInstance, PlayerId, TerritorySize, Troops}; + +/// Process player income at 10 TPS (once per turn) +/// Only runs when turn_is_ready() condition is true +/// +/// Uses Option<&BotPlayer> to distinguish bot vs human players: +/// - Some(&BotPlayer) = bot player (60% income, 33% max troops) +/// - None = human player (100% income, 100% max troops) +/// +/// On first run, this system also registers player entities if they haven't been registered yet +pub fn process_player_income_system(current_turn: Res, mut commands: Commands, mut game_instance: ResMut, query: Query<(Entity, Option<&BotPlayer>)>) { + // Check if entities need to be registered (first run) + let needs_registration = { + let player_manager = &game_instance.player_manager; + player_manager.get_players().first().is_some_and(|p| p.entity.is_none()) + }; + + if needs_registration { + // Register player entities on first run + let bot_player_ids: Vec = game_instance.bot_manager.bot_player_ids().to_vec(); + + for player in game_instance.player_manager.get_players_mut() { + let is_bot = bot_player_ids.contains(&player.id); + + let entity = if is_bot { commands.spawn((BotPlayer, PlayerId(player.id as u16), BorderTiles::default(), Troops(player.get_troops()), TerritorySize(player.get_territory_size()))).id() } else { commands.spawn((PlayerId(player.id as u16), BorderTiles::default(), Troops(player.get_troops()), TerritorySize(player.get_territory_size()))).id() }; + + player.entity = Some(entity); + } + + return; // Skip income processing on registration frame + } + + // Process income for all players + let player_manager = &mut game_instance.player_manager; + + for player in player_manager.get_players_mut() { + if !player.is_alive() { + continue; + } + + // Determine if this player is a bot by checking ECS entity + let is_bot = player.entity.and_then(|entity| query.get(entity).ok()).and_then(|(_, bot_marker)| bot_marker).is_some(); + + // Process income with bot modifier if applicable + player.income(is_bot); + } + + trace!("Income processed for turn {}", current_turn.turn.turn_number); +} diff --git a/crates/borders-core/src/game/input_handlers.rs b/crates/borders-core/src/game/input_handlers.rs new file mode 100644 index 0000000..91bffcc --- /dev/null +++ b/crates/borders-core/src/game/input_handlers.rs @@ -0,0 +1,266 @@ +//! Platform-agnostic input handling systems +//! +//! These systems use InputState instead of Bevy's input queries, +//! making them work across both WASM and desktop platforms with +//! Pixi.js rendering. + +use bevy_ecs::prelude::*; +use tracing::{debug, info, trace}; + +use crate::game::{GameAction, LocalPlayerContext, SpawnManager, TileOwnership}; +use crate::networking::{GameView, Intent, IntentEvent}; +use crate::ui::input::{InputState, KeyCode, MouseButton}; +use crate::ui::protocol::CameraCommand; + +/// Resource tracking whether spawn phase is active +#[derive(Resource, Default)] +pub struct SpawnPhase { + pub active: bool, +} + +/// Resource for attack control settings +#[derive(Resource)] +pub struct AttackControls { + pub attack_ratio: f32, +} + +impl Default for AttackControls { + fn default() -> Self { + Self { attack_ratio: 0.5 } + } +} + +/// Handle placing the human spawn by clicking on valid land +#[allow(clippy::too_many_arguments)] +pub fn handle_spawn_click_system(input_state: NonSend>>, spawn_phase: Res, game_view: Option>, local_context: Option>, mut spawn_manager: Option>, mut spawn_timeout: Option>, mut intent_writer: MessageWriter, game_instance: Option>) { + if !spawn_phase.active { + return; + } + + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.mouse_just_released(MouseButton::Left) { + return; + } + + let _span = tracing::trace_span!("spawn_click").entered(); + + // Frontend handles camera interaction filtering, but double-check here + if input.had_camera_interaction() { + trace!("Spawn click ignored - camera interaction detected"); + return; + } + + let Some(game_view) = game_view else { + debug!("Spawn click ignored - GameView not ready"); + return; + }; + + let Some(local_context) = local_context else { + debug!("Spawn click ignored - LocalPlayerContext not ready"); + return; + }; + + // Can't spawn if not allowed to send intents + if !local_context.can_send_intents { + debug!("Spawn click ignored - cannot send intents"); + return; + } + + // Get tile from InputState (set by frontend) + let Some(tile_coord) = input.cursor_tile() else { + debug!("Spawn click ignored - cursor not over valid tile"); + return; + }; + + let tile = tile_coord.to_index(game_view.width); + + let tile_ownership = TileOwnership::from_u16(game_view.get_owner(tile)); + if tile_ownership.is_owned() || tile_ownership.is_water() { + debug!("Spawn click on tile {} ignored - occupied or water", tile); + return; + } + + // Player has chosen a spawn location - send to server + info!("Player {} setting spawn at tile {}", local_context.my_player_id, tile); + + // Check if this is the first spawn (timer not started yet) + let is_first_spawn = if let Some(ref spawn_mgr) = spawn_manager { spawn_mgr.get_player_spawns().is_empty() } else { true }; + + // Send SetSpawn intent to server (not Action - this won't be in game history) + // Server will validate, track, and eventually send Turn(0) when timeout expires + intent_writer.write(IntentEvent(Intent::SetSpawn { player_id: local_context.my_player_id as u16, tile_index: tile as u32 })); + + // Start spawn timeout on first spawn (spawn_phase plugin will emit countdown updates) + if is_first_spawn && let Some(ref mut timeout) = spawn_timeout { + timeout.start(); + info!("Spawn timeout started ({:.1}s)", timeout.duration_secs); + } + + // Update local spawn manager for preview/bot recalculation + // Note: This only updates the spawn manager, not the game instance + // The actual game state is updated when Turn(0) is processed + if let Some(ref mut spawn_mgr) = spawn_manager + && let Some(ref game_inst) = game_instance + { + // Update spawn manager (triggers bot spawn recalculation) + spawn_mgr.update_player_spawn(local_context.my_player_id, tile, &game_inst.bot_manager, &game_inst.territory_manager); + + info!("Spawn manager updated with player {} spawn at tile {}", local_context.my_player_id, tile); + info!("Total spawns in manager: {}", spawn_mgr.get_all_spawns().len()); + } +} + +/// Center the camera on the client's spawn (hotkey C) +pub fn handle_center_camera_system(input_state: NonSend>>, game_view: Option>, local_context: Option>, mut camera_commands: MessageWriter) { + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.key_just_pressed(KeyCode::KeyC) { + return; + } + + let Some(game_view) = game_view else { + return; // GameView not ready yet + }; + + let Some(local_context) = local_context else { + return; // LocalPlayerContext not ready yet + }; + + // Find any owned tile to center on + if let Some(tile) = game_view.find_tile_owned_by(local_context.my_player_id as u16) { + camera_commands.write(CameraCommand::CenterOnTile { tile_index: tile as u32, animate: true }); + } +} + +/// After spawn, clicking tiles triggers expansion/attack based on ownership +/// Automatically detects if a ship is needed for water attacks +pub fn handle_attack_click_system(input_state: NonSend>>, spawn_phase: Res, game_view: Option>, game_instance: Option>, local_context: Option>, attack_controls: Res, mut intent_writer: MessageWriter) { + if spawn_phase.active { + return; + } + + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.mouse_just_released(MouseButton::Left) { + return; + } + + // Frontend handles camera interaction filtering + if input.had_camera_interaction() { + return; + } + + let _span = tracing::trace_span!("attack_click").entered(); + + let Some(game_view) = game_view else { + return; // GameView not ready yet + }; + + let Some(game_instance) = game_instance else { + return; // GameInstance not ready yet + }; + + let Some(local_context) = local_context else { + return; // LocalPlayerContext not ready yet + }; + + // Can't attack if not allowed to send intents (defeated/spectating) + if !local_context.can_send_intents { + return; + } + + // Get tile from InputState (set by frontend) + let Some(tile_coord) = input.cursor_tile() else { + return; + }; + + let tile = tile_coord.to_index(game_view.width); + let owner = game_view.get_owner(tile); + let player_id = local_context.my_player_id as u16; + + // Can't attack own tiles + if owner == player_id { + return; + } + + // Check if target is water - ignore water clicks + let width = game_view.width as usize; + if game_instance.terrain.is_navigable(((tile % width) as u32, (tile / width) as u32)) { + return; + } + + // Check if target is connected to player's territory + let height = game_view.height as usize; + + let is_connected = crate::game::connectivity::is_connected_to_player(game_view.territories.as_ref(), tile, player_id, width, height); + + if is_connected { + // Target is connected to player's territory - use normal attack + intent_writer.write(IntentEvent(Intent::Action(GameAction::Attack { player_id, target_tile: tile as u32, troops_ratio: attack_controls.attack_ratio }))); + return; + } + + // Target is NOT connected - need to use ship + debug!("Target {} not connected to player territory, attempting ship launch", tile); + + // Find target's nearest coastal tile + let target_coastal_tile = crate::game::connectivity::find_coastal_tile_in_region(game_view.territories.as_ref(), &game_instance.terrain, tile, width, height); + + let Some(target_coastal_tile) = target_coastal_tile else { + debug!("No coastal tile found in target's region for tile {}", tile); + return; + }; + + // Find player's nearest coastal tile + let player_border_tiles = game_instance.border_manager.get_border_tiles(player_id as usize); + let launch_tile = crate::game::ships::ShipManager::find_nearest_player_coastal_tile(&game_instance.coastal_tiles, player_border_tiles, target_coastal_tile, width); + + let Some(launch_tile) = launch_tile else { + debug!("Player has no coastal tiles to launch ship from"); + return; + }; + + debug!("Found launch tile {} and target coastal tile {} for target {}", launch_tile, target_coastal_tile, tile); + + // Try to find a water path from launch tile to target coastal tile + let path = crate::game::ships::ship_pathfinding::find_water_path(&game_instance.terrain, launch_tile, target_coastal_tile, crate::game::ships::SHIP_MAX_PATH_LENGTH); + + if let Some(_path) = path { + // We can reach the target by ship! + // Convert attack_ratio (0.0-1.0) to troops_percent (0-100) + let troops_percent = (attack_controls.attack_ratio * 100.0) as u32; + + debug!("Launching ship to target {} with {}% troops", tile, troops_percent); + + intent_writer.write(IntentEvent(Intent::Action(GameAction::LaunchShip { player_id, target_tile: tile as u32, troops_percent }))); + } else { + debug!("No water path found from {} to {}", launch_tile, target_coastal_tile); + } +} + +/// Adjust attack ratio with keys 1/2 +pub fn handle_attack_ratio_keys_system(input_state: NonSend>>, mut controls: ResMut) { + let Ok(input) = input_state.lock() else { + return; + }; + + let mut changed = false; + if input.key_just_pressed(KeyCode::Digit1) { + controls.attack_ratio = (controls.attack_ratio - 0.1).max(0.1); + changed = true; + } + if input.key_just_pressed(KeyCode::Digit2) { + controls.attack_ratio = (controls.attack_ratio + 0.1).min(1.0); + changed = true; + } + if changed { + debug!("Attack ratio changed to {:.1}", controls.attack_ratio); + } +} diff --git a/crates/borders-core/src/game/lifecycle.rs b/crates/borders-core/src/game/lifecycle.rs new file mode 100644 index 0000000..f634969 --- /dev/null +++ b/crates/borders-core/src/game/lifecycle.rs @@ -0,0 +1,171 @@ +use bevy_ecs::prelude::*; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use tracing::{debug, info}; + +use crate::constants::{BOT_COUNT, SPAWN_TIMEOUT_SECS}; +use crate::game::{ActiveAttacks, BorderManager, BotManager, GameInstance, HSLColor, LocalPlayerContext, Player, PlayerManager, SpawnManager, SpawnPhase, SpawnTimeout, TerritoryManager}; +use crate::networking::{GameView, LocalTurnServerHandle, PlayerView, TurnReceiver}; +use flume::Receiver; + +/// Parameters needed to initialize a new game +pub struct GameInitParams { + pub map_width: u32, + pub map_height: u32, + pub conquerable_tiles: Vec, + pub client_player_id: usize, + pub intent_rx: Receiver, + pub terrain_data: std::sync::Arc, +} + +/// Initialize all game resources when starting a new game +/// This should be called by the StartGame command handler +pub fn initialize_game_resources(commands: &mut Commands, params: GameInitParams) { + info!("Initializing game resources (map: {}x{}, player: {})", params.map_width, params.map_height, params.client_player_id); + + // Initialize territory manager + let mut territory_manager = TerritoryManager::new(params.map_width, params.map_height); + territory_manager.reset(params.map_width, params.map_height, ¶ms.conquerable_tiles); + debug!("Territory manager initialized with {} tiles", params.conquerable_tiles.len()); + + // Initialize border manager + let mut border_manager = BorderManager::new(); + border_manager.reset(params.map_width, params.map_height, 1 + BOT_COUNT); + + // Initialize active attacks + let mut active_attacks = ActiveAttacks::new(); + active_attacks.init(1 + BOT_COUNT, params.map_width); + + // Initialize bot manager (1 human player + BOT_COUNT bots) + let bot_manager = BotManager::new(BOT_COUNT, 1); + debug!("BotManager initialized with {} bots", BOT_COUNT); + + // Use a fixed seed for deterministic bot behavior and color generation + // In multiplayer, this should come from the server + let rng_seed = 0xDEADBEEF; + + // Create RNG for deterministic color generation + let mut rng = StdRng::seed_from_u64(rng_seed); + + // Create players: 1 human + BOT_COUNT bots + // Player IDs start at 0 (human), then 1, 2, 3... for bots + let mut players = Vec::new(); + + // Generate random hue offset for color spread + let hue_offset = rng.random_range(0.0..360.0); + + // All players (including human) get deterministically generated colors + for i in 0..=BOT_COUNT { + let is_human = i == 0; + let id = i as f32; + + // Use golden angle distribution with random offset for visually distinct colors + let hue = (id * 137.5 + hue_offset) % 360.0; + let saturation = rng.random_range(0.75..=0.95); + let lightness = rng.random_range(0.35..=0.65); + let color = HSLColor::new(hue, saturation, lightness); + + if is_human { + players.push(Player::new(i, "Player".to_string(), color)); + } else { + players.push(Player::new(i, format!("Bot {}", i), color)); + } + } + + // Initialize player manager + // Human player is always ID 0 + let mut player_manager = PlayerManager::new(); + let human_player_id = 0; + player_manager.init(players, human_player_id); + debug!("Player manager initialized with {} players (human: {}, bots: {})", 1 + BOT_COUNT, human_player_id, BOT_COUNT); + + // Create ship manager + let ship_manager = crate::game::ships::ShipManager::new(); + + // Create game instance (bots won't be spawned until player chooses spawn) + let game_instance = GameInstance::new(player_manager, territory_manager, active_attacks, border_manager, bot_manager, ship_manager, params.terrain_data.clone(), rng_seed); + + // Calculate initial bot spawn positions (first pass) + // These will be shown to the player, but not applied to game state yet + let initial_bot_spawns = game_instance.bot_manager.calculate_initial_spawns(&game_instance.territory_manager, rng_seed); + + debug!("Calculated {} initial bot spawn positions (requested: {})", initial_bot_spawns.len(), BOT_COUNT); + + if initial_bot_spawns.len() < BOT_COUNT { + tracing::warn!("Only {} of {} bots were able to spawn - map may be too small or bot count too high", initial_bot_spawns.len(), BOT_COUNT); + } + + // Create SpawnManager to track spawn positions during spawn phase + let spawn_manager = SpawnManager::new(initial_bot_spawns.clone(), rng_seed, params.map_width, params.map_height); + commands.insert_resource(spawn_manager); + + // Initialize GameView with initial game state + // Calculate total land tiles once for caching (performance optimization) + use std::sync::Arc; + + let total_land_tiles = game_instance.territory_manager.as_slice().iter().filter(|ownership| !ownership.is_water()).count() as u32; + + let game_view = GameView { + width: params.map_width, + height: params.map_height, + territories: Arc::from(game_instance.territory_manager.to_u16_vec().as_slice()), + turn_number: 0, + total_land_tiles, + changed_tiles: Vec::new(), // Empty on initialization + players: game_instance.player_manager.get_players().iter().map(|p| PlayerView { id: p.id as u16, color: p.color.to_rgba(), name: p.name.clone(), tile_count: p.get_territory_size() as u32, troops: p.get_troops() as u32, is_alive: p.is_alive() }).collect(), + ships: Vec::new(), // No ships at initialization + }; + + commands.insert_resource(game_instance); + commands.insert_resource(game_view); + debug!("GameInstance and GameView resources created"); + + // Initialize local player context + commands.insert_resource(LocalPlayerContext::new(0)); // Human player is ID 0 + debug!("LocalPlayerContext created for player 0 (human)"); + + // Initialize spawn timeout + commands.insert_resource(SpawnTimeout::new(SPAWN_TIMEOUT_SECS)); + debug!("SpawnTimeout initialized ({} seconds)", SPAWN_TIMEOUT_SECS); + + // Initialize turn generation resources + let (turn_tx, turn_rx) = flume::unbounded(); + let server_handle = LocalTurnServerHandle { paused: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)), running: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)) }; + commands.insert_resource(server_handle); + commands.insert_resource(TurnReceiver { turn_rx }); + commands.insert_resource(crate::networking::TurnGenerator { turn_number: 0, accumulated_time: 0.0, turn_tx, spawn_config: std::collections::HashMap::new(), spawn_timeout_accumulated: None, game_started: false }); + + debug!("Turn generator initialized (paused until player spawn)"); + + // Activate spawn phase (SpawnPhasePlugin will emit initial SpawnPhaseUpdate) + commands.insert_resource(SpawnPhase { active: true }); + debug!("Spawn phase activated"); + + info!("Game resources initialized successfully - ready to start"); +} + +/// Clean up all game resources when quitting a game +/// This should be called by the QuitGame command handler +pub fn cleanup_game_resources(world: &mut World) { + info!("Cleaning up game resources..."); + + // Stop local turn server if running + if let Some(server_handle) = world.get_resource::() { + server_handle.stop(); + world.remove_resource::(); + } + + // Remove all game-specific resources + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + + // Note: SpawnPhase is a permanent resource (init_resource), not removed on quit + + info!("Game resources cleaned up successfully"); +} diff --git a/crates/borders-core/src/game/local_context.rs b/crates/borders-core/src/game/local_context.rs new file mode 100644 index 0000000..61280f8 --- /dev/null +++ b/crates/borders-core/src/game/local_context.rs @@ -0,0 +1,71 @@ +use bevy_ecs::prelude::*; +use serde::{Deserialize, Serialize}; + +/// Represents the outcome for a specific player (local, not shared) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum PlayerOutcome { + /// Player has won the game + Victory, + /// Player has been eliminated/defeated + Defeat, +} + +/// Local player context - CLIENT-SPECIFIC state, NOT part of deterministic game state +/// +/// **Important: This is LOCAL context, not shared/deterministic state!** +/// +/// This resource contains information specific to THIS client's perspective: +/// - Which player ID this client controls +/// - Whether this player won/lost (irrelevant to other clients) +/// - Whether this client can send commands or is spectating +/// +/// This state is NOT synchronized across clients and is NOT part of +/// GameInstance/GameView which must be identical on all clients for determinism. +/// +/// In multiplayer: +/// - Each client has their own LocalPlayerContext with different player IDs +/// - One client may have `my_outcome = Victory` while others have `Defeat` +/// - A spectator would have `can_send_intents = false` +/// - The shared game state (GameInstance) continues running regardless +#[derive(Resource)] +pub struct LocalPlayerContext { + /// The player ID for this client + pub my_player_id: usize, + + /// The outcome for this specific player (if determined) + /// None = still playing, Some(Victory/Defeat) = game ended for this player + pub my_outcome: Option, + + /// Whether this client can send intents (false when defeated or spectating) + pub can_send_intents: bool, +} + +impl LocalPlayerContext { + /// Create a new local player context for the given player ID + pub fn new(player_id: usize) -> Self { + Self { my_player_id: player_id, my_outcome: None, can_send_intents: true } + } + + /// Mark the local player as defeated + pub fn mark_defeated(&mut self) { + self.my_outcome = Some(PlayerOutcome::Defeat); + self.can_send_intents = false; + } + + /// Mark the local player as victorious + pub fn mark_victorious(&mut self) { + self.my_outcome = Some(PlayerOutcome::Victory); + // Player can still send intents after victory (to continue playing if desired) + // Or set to false if you want to prevent further actions + } + + /// Check if the local player is still actively playing + pub fn is_playing(&self) -> bool { + self.my_outcome.is_none() && self.can_send_intents + } + + /// Check if the local player is spectating (defeated but watching) + pub fn is_spectating(&self) -> bool { + !self.can_send_intents + } +} diff --git a/crates/borders-core/src/game/mod.rs b/crates/borders-core/src/game/mod.rs new file mode 100644 index 0000000..8e21ad8 --- /dev/null +++ b/crates/borders-core/src/game/mod.rs @@ -0,0 +1,51 @@ +pub mod action; +pub mod borders; +pub mod bot; +pub mod combat; +pub mod connectivity; +pub mod constants; +pub mod game_instance; +pub mod income; +pub mod input_handlers; +pub mod lifecycle; +pub mod local_context; +pub mod outcome; +pub mod player; +pub mod player_manager; +pub mod rng; +pub mod ships; +pub mod spawn_manager; +pub mod spawn_timeout; +pub mod terrain; +pub mod territory; +pub mod territory_manager; +pub mod tile_ownership; +pub mod tilemap; +pub mod tilemap_changes; +pub mod turn; +pub mod utils; + +pub use action::*; +pub use borders::{BorderManager, BorderTransitionResult, update_player_borders_system}; +pub use bot::*; +pub use combat::{ActiveAttacks, AttackConfig, AttackExecutor}; +pub use connectivity::*; +pub use game_instance::*; +pub use income::process_player_income_system; +pub use input_handlers::*; +pub use lifecycle::*; +pub use local_context::*; +pub use outcome::*; +pub use player::{BorderTiles, BotPlayer, HSLColor, Player, PlayerId, TerritorySize, Troops}; +pub use player_manager::*; +pub use rng::DeterministicRng; +pub use ships::*; +pub use spawn_manager::*; +pub use spawn_timeout::*; +pub use terrain::*; +pub use territory::*; +pub use territory_manager::*; +pub use tile_ownership::*; +pub use tilemap::*; +pub use tilemap_changes::*; +pub use turn::{CurrentTurn, turn_is_ready}; diff --git a/crates/borders-core/src/game/outcome.rs b/crates/borders-core/src/game/outcome.rs new file mode 100644 index 0000000..2fb864c --- /dev/null +++ b/crates/borders-core/src/game/outcome.rs @@ -0,0 +1,82 @@ +use crate::game::local_context::LocalPlayerContext; +use crate::game::territory_manager::OWNER_WATER; +use crate::networking::GameView; +use crate::ui::protocol::{BackendMessage, GameOutcome}; +use bevy_ecs::prelude::*; +use tracing::info; + +/// System that checks if the local player has won or lost +/// This is a NON-BLOCKING check - the game continues running regardless +pub fn check_local_player_outcome(mut local_context: ResMut, game_view: Res, mut backend_messages: MessageWriter) { + // Don't check if outcome already determined + if local_context.my_outcome.is_some() { + return; + } + + // Don't check outcome until player has spawned + // Skip only if player has 0 tiles AND is_alive (hasn't spawned yet) + // If player has 0 tiles AND !is_alive, that's a real defeat + let my_player_id = local_context.my_player_id as u16; + let Some(my_player) = game_view.get_player(my_player_id) else { + return; + }; + + if my_player.tile_count == 0 && my_player.is_alive { + // Player hasn't spawned yet - skip outcome check + return; + } + + // Check defeat condition: I've been eliminated (0 tiles) + if !my_player.is_alive { + info!("Local player defeated - eliminated (0 tiles)"); + local_context.mark_defeated(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Defeat }); + return; + } + + // Calculate total claimable tiles for victory condition checks + let total_claimable_tiles = game_view.territories.iter().filter(|&&owner| owner != OWNER_WATER).count(); + + const WIN_THRESHOLD: f32 = 0.80; + + if total_claimable_tiles > 0 { + let my_tiles = my_player.tile_count as usize; + let my_occupation = my_tiles as f32 / total_claimable_tiles as f32; + + // Check if I've won by occupation + if my_occupation >= WIN_THRESHOLD { + info!("Local player victorious - reached {:.1}% occupation ({}/{} claimable tiles, threshold: {:.0}%)", my_occupation * 100.0, my_tiles, total_claimable_tiles, WIN_THRESHOLD * 100.0); + local_context.mark_victorious(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Victory }); + return; + } + + // Check if any opponent has won by occupation (which means I lost) + for player in &game_view.players { + if player.id != my_player_id && player.is_alive { + let opponent_tiles = player.tile_count as usize; + let opponent_occupation = opponent_tiles as f32 / total_claimable_tiles as f32; + + if opponent_occupation >= WIN_THRESHOLD { + info!("Local player defeated - {} reached {:.1}% occupation ({}/{} claimable tiles, threshold: {:.0}%)", player.name, opponent_occupation * 100.0, opponent_tiles, total_claimable_tiles, WIN_THRESHOLD * 100.0); + local_context.mark_defeated(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Defeat }); + return; + } + } + } + } + + // Check victory by eliminating all opponents + let all_opponents_dead = game_view + .players + .iter() + .filter(|p| p.id != my_player_id) // Exclude me + .all(|p| !p.is_alive); + + if all_opponents_dead && my_player.is_alive { + info!("Local player victorious - all opponents eliminated"); + local_context.mark_victorious(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Victory }); + } +} diff --git a/crates/borders-core/src/game/player.rs b/crates/borders-core/src/game/player.rs new file mode 100644 index 0000000..f909d05 --- /dev/null +++ b/crates/borders-core/src/game/player.rs @@ -0,0 +1,199 @@ +use bevy_ecs::prelude::*; +use std::collections::HashSet; + +/// Marker component to identify bot players +/// Human players are identified by the ABSENCE of this component +/// Use Option<&BotPlayer> in queries to handle both types +#[derive(Component, Debug, Clone, Copy, Default)] +pub struct BotPlayer; + +/// Player ID component for ECS queries +#[derive(Component, Debug, Clone, Copy)] +pub struct PlayerId(pub u16); + +/// Border tiles component - tiles at the edge of a player's territory +#[derive(Component, Debug, Clone, Default)] +pub struct BorderTiles(pub HashSet); + +/// Troops component - current troop count +#[derive(Component, Debug, Clone, Copy)] +pub struct Troops(pub f32); + +/// Territory size component - number of tiles owned +#[derive(Component, Debug, Clone, Copy)] +pub struct TerritorySize(pub usize); + +/// HSL Color representation +#[derive(Debug, Clone, Copy)] +pub struct HSLColor { + pub h: f32, // Hue: 0-360 + pub s: f32, // Saturation: 0-1 + pub l: f32, // Lightness: 0-1 +} + +impl HSLColor { + pub fn new(h: f32, s: f32, l: f32) -> Self { + Self { h, s, l } + } + + pub fn to_rgba(&self) -> [f32; 4] { + let c = (1.0 - (2.0 * self.l - 1.0).abs()) * self.s; + let h_prime = self.h / 60.0; + let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs()); + + let (r1, g1, b1) = if h_prime < 1.0 { + (c, x, 0.0) + } else if h_prime < 2.0 { + (x, c, 0.0) + } else if h_prime < 3.0 { + (0.0, c, x) + } else if h_prime < 4.0 { + (0.0, x, c) + } else if h_prime < 5.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + + let m = self.l - c / 2.0; + [r1 + m, g1 + m, b1 + m, 1.0] + } + + pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { + let r = r as f32 / 255.0; + let g = g as f32 / 255.0; + let b = b as f32 / 255.0; + + let max = r.max(g).max(b); + let min = r.min(g).min(b); + let delta = max - min; + + let l = (max + min) / 2.0; + + if delta == 0.0 { + return Self { h: 0.0, s: 0.0, l }; + } + + let s = if l < 0.5 { delta / (max + min) } else { delta / (2.0 - max - min) }; + + let h = if max == r { + 60.0 * (((g - b) / delta) % 6.0) + } else if max == g { + 60.0 * (((b - r) / delta) + 2.0) + } else { + 60.0 * (((r - g) / delta) + 4.0) + }; + + let h = if h < 0.0 { h + 360.0 } else { h }; + + Self { h, s, l } + } +} + +/// Nation in the game (controlled by either human player or bot AI) +#[derive(Debug, Clone)] +pub struct Nation { + pub id: usize, + pub name: String, + pub color: HSLColor, + troops: f32, + territory_size: usize, + alive: bool, + pub entity: Option, // Link to ECS entity +} + +impl Nation { + pub fn new(id: usize, name: String, color: HSLColor) -> Self { + Self { id, name, color, troops: 2500.0, territory_size: 0, alive: true, entity: None } + } + + /// Add a tile to the nation's territory + /// WARNING: Call this AFTER updating the territory manager + pub fn add_tile(&mut self, _tile: usize) { + self.territory_size += 1; + } + + /// Remove a tile from the nation's territory + /// WARNING: Call this AFTER updating the territory manager + pub fn remove_tile(&mut self, _tile: usize) { + self.territory_size = self.territory_size.saturating_sub(1); + if self.territory_size == 0 { + self.alive = false; + } + } + + /// Calculate maximum troop capacity based on territory size + /// 2 * (tiles^0.6 * 1000 + 50000) + pub fn calculate_max_troops(&self, is_bot: bool) -> f32 { + let base_max = 2.0 * ((self.territory_size as f32).powf(0.6) * 1000.0 + 50_000.0); + + if is_bot { + base_max * 0.33 // Bots get 33% max troops + } else { + base_max + } + } + + /// Calculate income for this tick + pub fn calculate_income(&self, is_bot: bool) -> f32 { + let max_troops = self.calculate_max_troops(is_bot); + + // Base income calculation + let mut income = 10.0 + (self.troops.powf(0.73) / 4.0); + + // Soft cap as approaching max troops + let ratio = 1.0 - (self.troops / max_troops); + income *= ratio; + + // Apply bot modifier + if is_bot { + income * 0.6 // Bots get 60% income + } else { + income + } + } + + /// Process one tick worth of income + pub fn income(&mut self, is_bot: bool) { + let income = self.calculate_income(is_bot); + self.add_troops_internal(income, is_bot); + } + + /// Internal method to add troops with max cap + fn add_troops_internal(&mut self, amount: f32, is_bot: bool) { + let max_troops = self.calculate_max_troops(is_bot); + self.troops = (self.troops + amount).min(max_troops); + } + + /// Get the amount of troops the nation has + pub fn get_troops(&self) -> f32 { + self.troops + } + + /// Add troops to the nation + /// Troops will be capped based on territory size using the max troops formula + /// For internal use; external callers should use the income system + pub fn add_troops(&mut self, amount: f32) { + // For external calls (attacks, donations), always use human formula + // This ensures consistent behavior for troop transfers + self.add_troops_internal(amount, false); + } + + /// Remove troops from the nation + pub fn remove_troops(&mut self, amount: f32) { + self.troops = (self.troops - amount).max(0.0); + } + + /// Get the size of the nation's territory (in tiles) + pub fn get_territory_size(&self) -> usize { + self.territory_size + } + + /// Check if the nation is alive + pub fn is_alive(&self) -> bool { + self.alive + } +} + +/// Type alias for backward compatibility during ECS migration +pub type Player = Nation; diff --git a/crates/borders-core/src/game/player_manager.rs b/crates/borders-core/src/game/player_manager.rs new file mode 100644 index 0000000..535bdb8 --- /dev/null +++ b/crates/borders-core/src/game/player_manager.rs @@ -0,0 +1,72 @@ +use crate::game::player::Player; + +/// Manages all players in the game +pub struct PlayerManager { + players: Vec, + pub client_player_id: usize, + human_count: usize, +} + +impl Default for PlayerManager { + fn default() -> Self { + Self::new() + } +} + +impl PlayerManager { + pub fn new() -> Self { + Self { players: Vec::new(), client_player_id: 0, human_count: 0 } + } + + /// Initialize the player manager + pub fn init(&mut self, players: Vec, client_player_id: usize) { + self.players = players; + self.client_player_id = client_player_id; + self.human_count = 1; // For now, only one human player + } + + /// Register a new player + pub fn register_player(&mut self, player: Player) { + self.players.push(player); + } + + /// Get a player by ID + pub fn get_player(&self, id: usize) -> Option<&Player> { + self.players.get(id) + } + + /// Get a mutable player by ID + pub fn get_player_mut(&mut self, id: usize) -> Option<&mut Player> { + self.players.get_mut(id) + } + + /// Get all players + pub fn get_players(&self) -> &[Player] { + &self.players + } + + /// Get all players mutably + pub fn get_players_mut(&mut self) -> &mut [Player] { + &mut self.players + } + + /// Check if a player is a bot + pub fn is_bot(&self, player_id: usize) -> bool { + player_id >= self.human_count + } + + /// Validate a player ID + pub fn validate_player(&self, player_id: usize) -> bool { + self.players.get(player_id).is_some_and(|p| p.is_alive()) + } + + /// Get the number of players + pub fn player_count(&self) -> usize { + self.players.len() + } + + /// Get the client player + pub fn get_client_player(&self) -> Option<&Player> { + self.get_player(self.client_player_id) + } +} diff --git a/crates/borders-core/src/game/rng.rs b/crates/borders-core/src/game/rng.rs new file mode 100644 index 0000000..4af16f1 --- /dev/null +++ b/crates/borders-core/src/game/rng.rs @@ -0,0 +1,121 @@ +use bevy_ecs::prelude::*; +use rand::SeedableRng; +use rand::rngs::StdRng; + +/// Centralized deterministic RNG resource +/// +/// This resource provides deterministic random number generation for all game systems. +/// It is updated at the start of each turn with the current turn number, ensuring that +/// the same sequence of turns always produces the same random values. +/// +/// # Determinism Guarantees +/// +/// - Same turn number + base seed + context → same RNG state +/// - No stored RNG state in individual systems (prevents desync) +/// - All randomness flows through this single source of truth +/// +/// # Usage +/// +/// Systems should never store RNG state. Instead, request context-specific RNG: +/// +/// ```rust,ignore +/// fn my_system(rng: Res) { +/// let mut player_rng = rng.for_player(player_id); +/// let random_value = player_rng.gen_range(0..10); +/// } +/// ``` +#[derive(Resource)] +pub struct DeterministicRng { + /// Base seed for the entire game (set at game start) + base_seed: u64, + /// Current turn number (updated each turn) + turn_number: u64, +} + +impl DeterministicRng { + /// Create a new DeterministicRng with a base seed + pub fn new(base_seed: u64) -> Self { + Self { base_seed, turn_number: 0 } + } + + /// Update the turn number (should be called at start of each turn) + pub fn update_turn(&mut self, turn_number: u64) { + self.turn_number = turn_number; + } + + /// Get the current turn number + pub fn turn_number(&self) -> u64 { + self.turn_number + } + + /// Create an RNG for a specific context within the current turn + /// + /// The context_id allows different systems/entities to have independent + /// random sequences while maintaining determinism. + pub fn for_context(&self, context_id: u64) -> StdRng { + let seed = self + .turn_number + .wrapping_mul(997) // Prime multiplier for turn + .wrapping_add(self.base_seed) + .wrapping_add(context_id.wrapping_mul(1009)); // Prime multiplier for context + StdRng::seed_from_u64(seed) + } + + /// Get an RNG for a specific player's actions this turn + /// + /// This is a convenience wrapper around `for_context` for player-specific randomness. + pub fn for_player(&self, player_id: usize) -> StdRng { + self.for_context(player_id as u64) + } + + /// Get an RNG for a specific tile's calculations this turn + /// + /// Useful for tile-based randomness that should be consistent within a turn. + pub fn for_tile(&self, tile_index: usize) -> StdRng { + // Use large offset to avoid collision with player IDs + self.for_context(1_000_000 + tile_index as u64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::Rng; + + #[test] + fn test_deterministic_same_turn_same_seed() { + let rng1 = DeterministicRng::new(12345); + let rng2 = DeterministicRng::new(12345); + + let mut player_rng1 = rng1.for_player(0); + let mut player_rng2 = rng2.for_player(0); + + assert_eq!(player_rng1.random::(), player_rng2.random::()); + } + + #[test] + fn test_deterministic_different_context() { + let rng = DeterministicRng::new(12345); + + let mut player0_rng = rng.for_player(0); + let mut player1_rng = rng.for_player(1); + + // Different contexts should produce different values + assert_ne!(player0_rng.random::(), player1_rng.random::()); + } + + #[test] + fn test_turn_update() { + let mut rng = DeterministicRng::new(12345); + + let mut turn0_rng = rng.for_player(0); + let value_turn0 = turn0_rng.random::(); + + rng.update_turn(1); + let mut turn1_rng = rng.for_player(0); + let value_turn1 = turn1_rng.random::(); + + // Same player, different turns should produce different values + assert_ne!(value_turn0, value_turn1); + } +} diff --git a/crates/borders-core/src/game/ships/mod.rs b/crates/borders-core/src/game/ships/mod.rs new file mode 100644 index 0000000..8160d86 --- /dev/null +++ b/crates/borders-core/src/game/ships/mod.rs @@ -0,0 +1,12 @@ +pub mod ship_manager; +pub mod ship_pathfinding; +pub mod transport_ship; + +pub use ship_manager::ShipManager; +pub use transport_ship::TransportShip; + +// Ship-related constants +pub const MAX_SHIPS_PER_PLAYER: usize = 5; +pub const SHIP_TICKS_PER_TILE: u32 = 1; // How many ticks to move one tile (1 = fast speed) +pub const SHIP_MAX_PATH_LENGTH: usize = 1_000_000; +pub const SHIP_TROOP_PERCENT: f32 = 0.20; diff --git a/crates/borders-core/src/game/ships/ship_manager.rs b/crates/borders-core/src/game/ships/ship_manager.rs new file mode 100644 index 0000000..32b9b04 --- /dev/null +++ b/crates/borders-core/src/game/ships/ship_manager.rs @@ -0,0 +1,158 @@ +use crate::game::ships::transport_ship::TransportShip; +use crate::game::ships::{MAX_SHIPS_PER_PLAYER, SHIP_MAX_PATH_LENGTH, SHIP_TICKS_PER_TILE}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tracing::debug; + +/// Manages all active ships in the game +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ShipManager { + /// All active ships, indexed by ship ID + ships: HashMap, + + /// Track number of ships per player + ships_per_player: HashMap, + + /// Next available ship ID + next_ship_id: u32, +} + +impl ShipManager { + pub fn new() -> Self { + Self { ships: HashMap::new(), ships_per_player: HashMap::new(), next_ship_id: 1 } + } + + /// Launch a new transport ship if possible + /// Returns Some(ship_id) if successful, None if launch failed + pub fn launch_ship(&mut self, owner_id: u16, troops: u32, path: Vec, launch_tick: u64) -> Option { + // Check if player has reached ship limit + let current_ships = *self.ships_per_player.get(&owner_id).unwrap_or(&0); + if current_ships >= MAX_SHIPS_PER_PLAYER { + debug!("Player {} cannot launch ship: already has {}/{} ships", owner_id, current_ships, MAX_SHIPS_PER_PLAYER); + return None; + } + + // Check path length + if path.is_empty() { + debug!("Cannot launch ship with empty path"); + return None; + } + + if path.len() > SHIP_MAX_PATH_LENGTH { + debug!("Cannot launch ship: path too long ({} > {})", path.len(), SHIP_MAX_PATH_LENGTH); + return None; + } + + // Create the ship + let ship_id = self.next_ship_id; + self.next_ship_id += 1; + + let ship = TransportShip::new(ship_id, owner_id, troops, path, SHIP_TICKS_PER_TILE, launch_tick); + + self.ships.insert(ship_id, ship); + *self.ships_per_player.entry(owner_id).or_insert(0) += 1; + + debug!("Launched ship {} for player {} with {} troops", ship_id, owner_id, troops); + + Some(ship_id) + } + + /// Update all ships and return list of ships that arrived at destination + /// Returns: Vec<(owner_id, target_tile, troops)> + pub fn update_ships(&mut self) -> Vec<(u16, usize, u32)> { + let mut arrivals = Vec::new(); + let mut ships_to_remove = Vec::new(); + + for (ship_id, ship) in self.ships.iter_mut() { + if ship.update() { + // Ship has arrived at destination + arrivals.push((ship.owner_id, ship.target_tile, ship.troops)); + ships_to_remove.push(*ship_id); + } + } + + // Remove arrived ships + for ship_id in ships_to_remove { + if let Some(ship) = self.ships.remove(&ship_id) { + if let Some(count) = self.ships_per_player.get_mut(&ship.owner_id) { + *count = count.saturating_sub(1); + if *count == 0 { + self.ships_per_player.remove(&ship.owner_id); + } + } + debug!("Ship {} arrived at destination with {} troops", ship_id, ship.troops); + } + } + + arrivals + } + + /// Get all active ships + pub fn get_ships(&self) -> impl Iterator { + self.ships.values() + } + + /// Get ships for a specific player + pub fn get_player_ships(&self, player_id: u16) -> impl Iterator { + self.ships.values().filter(move |ship| ship.owner_id == player_id) + } + + /// Get number of ships for a player + pub fn get_ship_count(&self, player_id: u16) -> usize { + *self.ships_per_player.get(&player_id).unwrap_or(&0) + } + + /// Remove all ships for a player (e.g., when player is eliminated) + pub fn remove_player_ships(&mut self, player_id: u16) { + let ships_to_remove: Vec = self.ships.iter().filter(|(_, ship)| ship.owner_id == player_id).map(|(id, _)| *id).collect(); + + for ship_id in ships_to_remove { + self.ships.remove(&ship_id); + } + + self.ships_per_player.remove(&player_id); + } + + /// Clear all ships + pub fn clear(&mut self) { + self.ships.clear(); + self.ships_per_player.clear(); + self.next_ship_id = 1; + } + + /// Find the nearest coastal tile owned by a player to a target tile + /// Uses precomputed coastal tiles and player's border tiles for efficiency + /// Returns None if no valid coastal tile found + pub fn find_nearest_player_coastal_tile(coastal_tiles: &std::collections::HashSet, player_border_tiles: &std::collections::HashSet, target_tile: usize, width: usize) -> Option { + let target_x = target_tile % width; + let target_y = target_tile / width; + + let mut best_tile = None; + let mut best_distance = usize::MAX; + + // Filter player's border tiles for coastal ones + for &tile in player_border_tiles { + // Check if this tile is in the coastal set + if coastal_tiles.contains(&tile) { + let tile_x = tile % width; + let tile_y = tile / width; + + // Calculate Manhattan distance to target + let dist = ((tile_x as i32 - target_x as i32).abs() + (tile_y as i32 - target_y as i32).abs()) as usize; + + if dist < best_distance { + best_distance = dist; + best_tile = Some(tile); + } + } + } + + best_tile + } +} + +impl Default for ShipManager { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/borders-core/src/game/ships/ship_pathfinding.rs b/crates/borders-core/src/game/ships/ship_pathfinding.rs new file mode 100644 index 0000000..5664011 --- /dev/null +++ b/crates/borders-core/src/game/ships/ship_pathfinding.rs @@ -0,0 +1,310 @@ +use crate::game::terrain::TerrainData; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashMap, HashSet}; +use tracing::debug; + +/// A node in the pathfinding search +#[derive(Clone, Eq, PartialEq)] +struct PathNode { + tile_idx: usize, + g_cost: u32, // Cost from start + h_cost: u32, // Heuristic cost to goal + f_cost: u32, // Total cost (g + h) + parent: Option, +} + +impl Ord for PathNode { + fn cmp(&self, other: &Self) -> Ordering { + // Reverse ordering for min-heap + other.f_cost.cmp(&self.f_cost) + } +} + +impl PartialOrd for PathNode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Find a water path from start_tile to target_tile using A* algorithm +/// Returns None if no path exists +pub fn find_water_path(terrain: &TerrainData, start_tile: usize, target_tile: usize, max_path_length: usize) -> Option> { + let width = terrain.size().x as usize; + let height = terrain.size().y as usize; + + // Check if target is reachable (must be coastal or water) + if !is_valid_ship_destination(terrain, target_tile, width, height) { + debug!("Pathfinding failed: target {} is not a valid ship destination", target_tile); + return None; + } + + // Find actual water start position (adjacent to coast) + debug!("Pathfinding: looking for water launch tile adjacent to coastal tile {}", start_tile); + let water_start = find_water_launch_tile(terrain, start_tile, width, height)?; + debug!("Pathfinding: found water launch tile {}", water_start); + + // Find water tiles adjacent to target if target is land + let target_x = target_tile % width; + let target_y = target_tile / width; + let water_targets = if terrain.is_navigable((target_x as u32, target_y as u32)) { vec![target_tile] } else { find_adjacent_water_tiles(terrain, target_tile, width, height) }; + + if water_targets.is_empty() { + return None; + } + + // Run A* pathfinding + let mut open_set = BinaryHeap::new(); + let mut closed_set = HashSet::new(); + let mut came_from: HashMap = HashMap::new(); + let mut g_scores: HashMap = HashMap::new(); + + // Initialize with start node + let start_h = heuristic_distance(water_start, water_targets[0], width); + open_set.push(PathNode { tile_idx: water_start, g_cost: 0, h_cost: start_h, f_cost: start_h, parent: None }); + g_scores.insert(water_start, 0); + + while let Some(current_node) = open_set.pop() { + let current = current_node.tile_idx; + + // Check if we've reached any of the target tiles + if water_targets.contains(¤t) { + // Reconstruct path + let mut path = vec![current]; + let mut current_tile = current; + + while let Some(&parent) = came_from.get(¤t_tile) { + path.push(parent); + current_tile = parent; + + // Prevent infinite loops + if path.len() > max_path_length { + return None; + } + } + + path.reverse(); + + // If original target was land, add it to the end + if !terrain.is_navigable((target_x as u32, target_y as u32)) { + path.push(target_tile); + } + + return Some(path); + } + + // Skip if already processed + if closed_set.contains(¤t) { + continue; + } + closed_set.insert(current); + + // Check if we've exceeded max path length + if current_node.g_cost as usize > max_path_length { + continue; + } + + // Explore neighbors + let neighbors = get_water_neighbors(terrain, current, width, height); + for neighbor in neighbors { + if closed_set.contains(&neighbor) { + continue; + } + + let tentative_g = current_node.g_cost + 1; + + if tentative_g < *g_scores.get(&neighbor).unwrap_or(&u32::MAX) { + came_from.insert(neighbor, current); + g_scores.insert(neighbor, tentative_g); + + // Find best heuristic to any target + let h_cost = water_targets.iter().map(|&t| heuristic_distance(neighbor, t, width)).min().unwrap_or(0); + + let f_cost = tentative_g + h_cost; + + open_set.push(PathNode { tile_idx: neighbor, g_cost: tentative_g, h_cost, f_cost, parent: Some(current) }); + } + } + } + + debug!("Pathfinding failed: no path found from {} to {}", start_tile, target_tile); + None // No path found +} + +/// Calculate heuristic distance between two tiles (Manhattan distance) +fn heuristic_distance(from: usize, to: usize, width: usize) -> u32 { + let from_x = from % width; + let from_y = from / width; + let to_x = to % width; + let to_y = to / width; + + ((from_x as i32 - to_x as i32).abs() + (from_y as i32 - to_y as i32).abs()) as u32 +} + +/// Get water neighbors of a tile (4-directional movement) +fn get_water_neighbors(terrain: &TerrainData, tile: usize, width: usize, height: usize) -> Vec { + let x = tile % width; + let y = tile / width; + let mut neighbors = Vec::with_capacity(4); + + // Check 4 directions + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height { + let neighbor_idx = nx as usize + ny as usize * width; + if terrain.is_navigable((nx as u32, ny as u32)) { + neighbors.push(neighbor_idx); + } + } + } + + neighbors +} + +/// Find a water tile adjacent to a coastal land tile for ship launch +fn find_water_launch_tile(terrain: &TerrainData, coast_tile: usize, width: usize, height: usize) -> Option { + let x = coast_tile % width; + let y = coast_tile / width; + + debug!("find_water_launch_tile: checking coastal tile {} at ({},{})", coast_tile, x, y); + + // Check 4 directions for water + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height { + let neighbor_idx = nx as usize + ny as usize * width; + let is_water = terrain.is_navigable((nx as u32, ny as u32)); + debug!(" Checking neighbor ({},{}) -> tile {}: is_water={}", nx, ny, neighbor_idx, is_water); + if is_water { + debug!(" Found water launch tile {} at ({},{})", neighbor_idx, nx, ny); + return Some(neighbor_idx); + } + } + } + + debug!(" No water launch tile found for coastal tile {}", coast_tile); + None +} + +/// Find all water tiles adjacent to a land tile +fn find_adjacent_water_tiles(terrain: &TerrainData, tile: usize, width: usize, height: usize) -> Vec { + let x = tile % width; + let y = tile / width; + let mut water_tiles = Vec::new(); + + // Check 4 directions for water + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height { + let neighbor_idx = nx as usize + ny as usize * width; + if terrain.is_navigable((nx as u32, ny as u32)) { + water_tiles.push(neighbor_idx); + } + } + } + + water_tiles +} + +/// Check if a tile is a valid ship destination (water or coastal land) +fn is_valid_ship_destination(terrain: &TerrainData, tile: usize, width: usize, height: usize) -> bool { + let x = tile % width; + let y = tile / width; + + // If it's water, it's valid + if terrain.is_navigable((x as u32, y as u32)) { + return true; + } + + // If it's land, check if it's coastal + let directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]; + + for (dx, dy) in directions { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + + if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height && terrain.is_navigable((nx as u32, ny as u32)) { + return true; // It's coastal + } + } + + false +} + +/// Simplify a path by removing unnecessary waypoints (path smoothing) +/// This maintains determinism as it's purely geometric +pub fn smooth_path(path: Vec, terrain: &TerrainData, width: usize) -> Vec { + if path.len() <= 2 { + return path; + } + + let mut smoothed = vec![path[0]]; + let mut current = 0; + + while current < path.len() - 1 { + let mut farthest = current + 1; + + // Find the farthest point we can see directly + for i in (current + 2)..path.len() { + if has_clear_water_line(terrain, path[current], path[i], width) { + farthest = i; + } else { + break; + } + } + + smoothed.push(path[farthest]); + current = farthest; + } + + smoothed +} + +/// Check if there's a clear water line between two tiles +/// Uses Bresenham-like algorithm for deterministic line checking +fn has_clear_water_line(terrain: &TerrainData, from: usize, to: usize, width: usize) -> bool { + let x0 = (from % width) as i32; + let y0 = (from / width) as i32; + let x1 = (to % width) as i32; + let y1 = (to / width) as i32; + + let dx = (x1 - x0).abs(); + let dy = (y1 - y0).abs(); + let sx = if x0 < x1 { 1 } else { -1 }; + let sy = if y0 < y1 { 1 } else { -1 }; + let mut err = dx - dy; + + let mut x = x0; + let mut y = y0; + + loop { + if !terrain.is_navigable((x as u32, y as u32)) { + return false; // Hit land + } + + if x == x1 && y == y1 { + return true; // Reached target + } + + let e2 = 2 * err; + if e2 > -dy { + err -= dy; + x += sx; + } + if e2 < dx { + err += dx; + y += sy; + } + } +} diff --git a/crates/borders-core/src/game/ships/transport_ship.rs b/crates/borders-core/src/game/ships/transport_ship.rs new file mode 100644 index 0000000..b940280 --- /dev/null +++ b/crates/borders-core/src/game/ships/transport_ship.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; + +/// A transport ship carrying troops across water +/// Uses fixed-point arithmetic for deterministic calculations +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TransportShip { + /// Unique identifier for this ship + pub id: u32, + + /// Player who owns this ship + pub owner_id: u16, + + /// Number of troops being transported (stored as integer) + pub troops: u32, + + /// Pre-calculated path of tile indices + pub path: Vec, + + /// Index of the current tile in the path + pub current_path_index: usize, + + /// Movement speed in ticks per tile (e.g., 2 = move one tile every 2 ticks) + pub ticks_per_tile: u32, + + /// Ticks since last tile transition + pub ticks_since_move: u32, + + /// The tick when the ship was launched + pub launch_tick: u64, + + /// The target tile (final destination) + pub target_tile: usize, +} + +impl TransportShip { + /// Create a new transport ship + pub fn new(id: u32, owner_id: u16, troops: u32, path: Vec, ticks_per_tile: u32, launch_tick: u64) -> Self { + let target_tile = *path.last().unwrap_or(&path[0]); + + Self { id, owner_id, troops, path, current_path_index: 0, ticks_per_tile, ticks_since_move: 0, launch_tick, target_tile } + } + + /// Update the ship's position based on the current tick + /// Returns true if the ship has reached its destination + pub fn update(&mut self) -> bool { + if self.has_arrived() { + return true; + } + + self.ticks_since_move += 1; + + // Check if it's time to move to the next tile + if self.ticks_since_move >= self.ticks_per_tile { + self.ticks_since_move = 0; + self.current_path_index += 1; + + if self.has_arrived() { + return true; + } + } + + false + } + + /// Get the current tile the ship is on + pub fn get_current_tile(&self) -> usize { + if self.current_path_index < self.path.len() { self.path[self.current_path_index] } else { self.target_tile } + } + + /// Check if the ship has reached its destination + pub fn has_arrived(&self) -> bool { + self.current_path_index >= self.path.len() - 1 + } + + /// Get interpolation factor for smooth rendering (0.0 to 1.0) + /// This is only for visual interpolation and doesn't affect game logic + pub fn get_visual_interpolation(&self) -> f32 { + if self.ticks_per_tile == 0 { + return 1.0; + } + self.ticks_since_move as f32 / self.ticks_per_tile as f32 + } + + /// Get the next tile in the path (for interpolation) + pub fn get_next_tile(&self) -> Option { + if self.current_path_index + 1 < self.path.len() { Some(self.path[self.current_path_index + 1]) } else { None } + } +} diff --git a/crates/borders-core/src/game/spawn_manager.rs b/crates/borders-core/src/game/spawn_manager.rs new file mode 100644 index 0000000..1f35eba --- /dev/null +++ b/crates/borders-core/src/game/spawn_manager.rs @@ -0,0 +1,67 @@ +use bevy_ecs::prelude::*; + +/// Manages spawn positions during the pre-game spawn phase +/// +/// This resource tracks bot and player spawn positions before the game starts ticking. +/// It allows for dynamic recalculation of bot positions when players change their spawn +/// location, implementing the two-pass spawn system described in the README. +#[derive(Resource)] +pub struct SpawnManager { + /// Initial bot spawn positions from first pass (player_id, tile_index) + pub initial_bot_spawns: Vec<(usize, usize)>, + + /// Current bot spawn positions after recalculation (player_id, tile_index) + /// These are updated whenever a player chooses/changes their spawn + pub current_bot_spawns: Vec<(usize, usize)>, + + /// Player spawn positions (player_id, tile_index) + /// Tracks human player spawn selections + pub player_spawns: Vec<(usize, usize)>, + + /// RNG seed for deterministic spawn calculations + pub rng_seed: u64, + + /// Map dimensions for distance calculations + pub map_width: u32, + pub map_height: u32, +} + +impl SpawnManager { + /// Create a new SpawnManager with initial bot spawns + pub fn new(initial_bot_spawns: Vec<(usize, usize)>, rng_seed: u64, map_width: u32, map_height: u32) -> Self { + Self { current_bot_spawns: initial_bot_spawns.clone(), initial_bot_spawns, player_spawns: Vec::new(), rng_seed, map_width, map_height } + } + + /// Update a player's spawn position and recalculate bot spawns if necessary + /// + /// This triggers the second pass of the two-pass spawn system, relocating + /// any bots that are too close to the new player position. + pub fn update_player_spawn(&mut self, player_id: usize, tile_index: usize, bot_manager: &crate::game::BotManager, territory_manager: &crate::game::TerritoryManager) { + // Update or add player spawn + if let Some(entry) = self.player_spawns.iter_mut().find(|(pid, _)| *pid == player_id) { + entry.1 = tile_index; + } else { + self.player_spawns.push((player_id, tile_index)); + } + + // Recalculate bot spawns with updated player positions + self.current_bot_spawns = bot_manager.recalculate_spawns_with_players(self.initial_bot_spawns.clone(), &self.player_spawns, territory_manager, self.rng_seed); + } + + /// Get all current spawn positions (players + bots) + pub fn get_all_spawns(&self) -> Vec<(usize, usize)> { + let mut all_spawns = self.player_spawns.clone(); + all_spawns.extend(self.current_bot_spawns.iter().copied()); + all_spawns + } + + /// Get only bot spawn positions + pub fn get_bot_spawns(&self) -> &[(usize, usize)] { + &self.current_bot_spawns + } + + /// Get only player spawn positions + pub fn get_player_spawns(&self) -> &[(usize, usize)] { + &self.player_spawns + } +} diff --git a/crates/borders-core/src/game/spawn_timeout.rs b/crates/borders-core/src/game/spawn_timeout.rs new file mode 100644 index 0000000..fd49e19 --- /dev/null +++ b/crates/borders-core/src/game/spawn_timeout.rs @@ -0,0 +1,74 @@ +use bevy_ecs::prelude::*; + +/// Tracks spawn phase timeout state on the client side +/// +/// This resource is used to: +/// - Show countdown timer in UI +/// - Know when spawn phase is active +/// - Calculate remaining time for display +#[derive(Resource)] +pub struct SpawnTimeout { + /// Whether spawn phase is currently active + pub active: bool, + + /// Accumulated time since start (seconds) + pub elapsed_secs: f32, + + /// Total timeout duration in seconds + pub duration_secs: f32, + + /// Remaining time in seconds (updated each frame) + pub remaining_secs: f32, +} + +impl Default for SpawnTimeout { + fn default() -> Self { + Self { + active: false, + elapsed_secs: 0.0, + duration_secs: 5.0, // Local mode: 5 seconds + remaining_secs: 5.0, + } + } +} + +impl SpawnTimeout { + /// Create a new spawn timeout with specified duration + pub fn new(duration_secs: f32) -> Self { + Self { active: false, elapsed_secs: 0.0, duration_secs, remaining_secs: duration_secs } + } + + /// Start the timeout countdown + pub fn start(&mut self) { + if self.elapsed_secs == 0.0 { + self.active = true; + self.elapsed_secs = 0.0; + self.remaining_secs = self.duration_secs; + } + } + + /// Update remaining time (call each frame with delta time) + pub fn update(&mut self, delta_secs: f32) { + if !self.active { + return; + } + + self.elapsed_secs += delta_secs; + self.remaining_secs = (self.duration_secs - self.elapsed_secs).max(0.0); + + if self.remaining_secs <= 0.0 { + self.active = false; + } + } + + /// Stop the timeout + pub fn stop(&mut self) { + self.active = false; + self.elapsed_secs = 0.0; + } + + /// Check if timeout has expired + pub fn has_expired(&self) -> bool { + !self.active && self.remaining_secs <= 0.0 + } +} diff --git a/crates/borders-core/src/game/terrain.rs b/crates/borders-core/src/game/terrain.rs new file mode 100644 index 0000000..45abe1b --- /dev/null +++ b/crates/borders-core/src/game/terrain.rs @@ -0,0 +1,270 @@ +use bevy_ecs::prelude::Resource; +use glam::UVec2; +use image::GenericImageView; +use serde::{Deserialize, Serialize}; +use std::fs; +use tracing::{debug, info}; + +use crate::game::territory::get_idx; +use crate::game::tilemap::TileMap; + +/// Calculate terrain color using pastel theme formulas +fn calculate_theme_color(color_base: &str, color_variant: u8) -> [u8; 3] { + let i = color_variant as i32; + + match color_base { + "grass" => { + // rgb(238 - 2 * i, 238 - 2 * i, 190 - i) + [(238 - 2 * i).clamp(0, 255) as u8, (238 - 2 * i).clamp(0, 255) as u8, (190 - i).clamp(0, 255) as u8] + } + "mountain" => { + // rgb(250 - 2 * i, 250 - 2 * i, 220 - i) + [(250 - 2 * i).clamp(0, 255) as u8, (250 - 2 * i).clamp(0, 255) as u8, (220 - i).clamp(0, 255) as u8] + } + "water" => { + // rgb(172 - 2 * i, 225 - 2 * i, 249 - 3 * i) + [(172 - 2 * i).clamp(0, 255) as u8, (225 - 2 * i).clamp(0, 255) as u8, (249 - 3 * i).clamp(0, 255) as u8] + } + _ => { + // Default fallback color (gray) + [128, 128, 128] + } + } +} + +/// Helper structs for loading World.json format +#[derive(Deserialize)] +struct WorldMapJson { + tiles: Vec, +} + +#[derive(Deserialize)] +struct WorldTileDef { + color: String, + name: String, + #[serde(default, rename = "colorBase")] + color_base: Option, + #[serde(default, rename = "colorVariant")] + color_variant: Option, + conquerable: bool, + navigable: bool, + #[serde(default, rename = "expansionCost")] + expansion_cost: Option, + #[serde(default, rename = "expansionTime")] + expansion_time: Option, +} + +/// Parse hex color string (#RRGGBB) to RGB bytes +fn parse_hex_rgb(s: &str) -> Option<[u8; 3]> { + let s = s.trim_start_matches('#'); + if s.len() != 6 { + return None; + } + let r = u8::from_str_radix(&s[0..2], 16).ok()?; + let g = u8::from_str_radix(&s[2..4], 16).ok()?; + let b = u8::from_str_radix(&s[4..6], 16).ok()?; + Some([r, g, b]) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TileType { + pub name: String, + pub color_base: String, + pub color_variant: u8, + pub conquerable: bool, + pub navigable: bool, + pub expansion_time: u8, + pub expansion_cost: u8, +} + +/// Map manifest structure +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MapManifest { + pub map: MapMetadata, + pub name: String, + pub nations: Vec, +} + +/// Map size metadata +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MapMetadata { + pub width: usize, + pub height: usize, + pub num_land_tiles: usize, +} + +/// Nation spawn point +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct NationSpawn { + pub coordinates: [usize; 2], + pub flag: String, + pub name: String, + pub strength: u32, +} + +/// Loaded map data +#[derive(Debug, Clone, Resource)] +pub struct TerrainData { + pub _manifest: MapManifest, + /// Legacy terrain data (for backward compatibility) + pub terrain_data: TileMap, + /// Tile type indices (new format) + pub tiles: Vec, + /// Tile type definitions + pub tile_types: Vec, +} + +impl TerrainData { + /// Load the World map from embedded assets + pub fn load_world_map() -> Result> { + const MAP_JSON: &[u8] = include_bytes!("../../assets/maps/World.json"); + const MAP_PNG: &[u8] = include_bytes!("../../assets/maps/World.png"); + + // Parse JSON tile definitions + let map_json: WorldMapJson = serde_json::from_slice(MAP_JSON)?; + + // Load PNG image + let png = image::load_from_memory(MAP_PNG)?; + let (width, height) = png.dimensions(); + + info!("Loading World map: {}x{}", width, height); + + // Build color-to-index lookup table + let color_to_index: Vec<([u8; 3], usize)> = map_json.tiles.iter().enumerate().filter_map(|(idx, t)| parse_hex_rgb(&t.color).map(|rgb| (rgb, idx))).collect(); + + let mut tiles = vec![0u8; (width * height) as usize]; + let mut terrain_data_raw = vec![0u8; (width * height) as usize]; + + // Match each pixel to nearest tile type by color + for y in 0..height { + for x in 0..width { + let pixel = png.get_pixel(x, y).0; + let rgb = [pixel[0], pixel[1], pixel[2]]; + + // Find nearest tile by RGB distance + let (tile_idx, _) = color_to_index + .iter() + .map(|(c, idx)| { + let dr = rgb[0] as i32 - c[0] as i32; + let dg = rgb[1] as i32 - c[1] as i32; + let db = rgb[2] as i32 - c[2] as i32; + let dist = (dr * dr + dg * dg + db * db) as u32; + (idx, dist) + }) + .min_by_key(|(_, d)| *d) + .unwrap(); + + let i = (y * width + x) as usize; + tiles[i] = *tile_idx as u8; + + // Set bit 7 if conquerable (land) + if map_json.tiles[*tile_idx].conquerable { + terrain_data_raw[i] |= 0x80; + } + // Lower 5 bits for terrain magnitude (unused for World map) + } + } + + // Convert to TileType format + let tile_types = map_json.tiles.into_iter().map(|t| TileType { name: t.name, color_base: t.color_base.unwrap_or_default(), color_variant: t.color_variant.unwrap_or(0) as u8, conquerable: t.conquerable, navigable: t.navigable, expansion_cost: t.expansion_cost.unwrap_or(50) as u8, expansion_time: t.expansion_time.unwrap_or(50) as u8 }).collect(); + + let num_land_tiles = terrain_data_raw.iter().filter(|&&b| b & 0x80 != 0).count(); + + info!("World map loaded: {} land tiles", num_land_tiles); + + Ok(Self { _manifest: MapManifest { name: "World".to_string(), map: MapMetadata { width: width as usize, height: height as usize, num_land_tiles }, nations: vec![] }, terrain_data: TileMap::from_vec(width, height, terrain_data_raw), tiles, tile_types }) + } + + /// Load a map from the resources directory + pub fn load(map_name: &str) -> Result> { + let base_path = format!("resources/maps/{}", map_name); + + // Load manifest + let manifest_path = format!("{}/manifest.json", base_path); + let manifest_json = fs::read_to_string(&manifest_path)?; + let manifest: MapManifest = serde_json::from_str(&manifest_json)?; + + // Load binary map data + let map_path = format!("{}/map.bin", base_path); + let terrain_data_raw = fs::read(&map_path)?; + + let width = manifest.map.width as u32; + let height = manifest.map.height as u32; + + // Verify data size + if terrain_data_raw.len() != (width * height) as usize { + return Err(format!("Map data size mismatch: expected {} bytes, got {}", width * height, terrain_data_raw.len()).into()); + } + + info!("Loaded map '{}' ({}x{})", manifest.name, width, height); + debug!("Land tiles: {}/{}", manifest.map.num_land_tiles, width * height); + + // Create default tile types for legacy format + let tile_types = vec![TileType { name: "water".to_string(), color_base: "water".to_string(), color_variant: 0, conquerable: false, navigable: true, expansion_time: 255, expansion_cost: 255 }, TileType { name: "land".to_string(), color_base: "grass".to_string(), color_variant: 0, conquerable: true, navigable: false, expansion_time: 50, expansion_cost: 50 }]; + + // Convert legacy format to tile indices + let tiles: Vec = terrain_data_raw.iter().map(|&byte| if byte & 0x80 != 0 { 1 } else { 0 }).collect(); + + // Create TileMap from terrain data + let terrain_data = TileMap::from_vec(width, height, terrain_data_raw); + + Ok(Self { _manifest: manifest, terrain_data, tiles, tile_types }) + } + + /// Get the size of the map + pub fn size(&self) -> UVec2 { + UVec2::new(self.terrain_data.width(), self.terrain_data.height()) + } + + pub fn get_value>(&self, pos: T) -> u8 { + self.terrain_data[get_idx(pos, self.terrain_data.width())] + } + + /// Check if a tile is land (bit 7 set) + pub fn is_land>(&self, pos: T) -> bool { + self.get_value(pos) & 0x80 != 0 + } + + /// Get terrain magnitude (bits 0-4) + pub fn terrain_magnitude>(&self, pos: T) -> u8 { + self.get_value(pos) & 0b00011111 + } + + /// Get tile type at position + pub fn get_tile_type>(&self, pos: T) -> &TileType { + let idx = get_idx(pos, self.terrain_data.width()); + &self.tile_types[self.tiles[idx] as usize] + } + + /// Check if a tile is conquerable + pub fn is_conquerable>(&self, pos: T) -> bool { + self.get_tile_type(pos).conquerable + } + + /// Check if a tile is navigable (water) + pub fn is_navigable>(&self, pos: T) -> bool { + self.get_tile_type(pos).navigable + } + + /// Get expansion time for a tile + pub fn get_expansion_time>(&self, pos: T) -> u8 { + self.get_tile_type(pos).expansion_time + } + + /// Get expansion cost for a tile + pub fn get_expansion_cost>(&self, pos: T) -> u8 { + self.get_tile_type(pos).expansion_cost + } + + /// Get tile type IDs for rendering (each position maps to a tile type) + pub fn get_tile_ids(&self) -> &[u8] { + &self.tiles + } + + /// Get terrain palette colors from tile types (for rendering) + /// Returns a vec where index = tile type ID, value = RGB color + /// Colors are calculated using theme formulas based on colorBase and colorVariant + pub fn get_terrain_palette_colors(&self) -> Vec<[u8; 3]> { + self.tile_types.iter().map(|tile_type| calculate_theme_color(&tile_type.color_base, tile_type.color_variant)).collect() + } +} diff --git a/crates/borders-core/src/game/territory.rs b/crates/borders-core/src/game/territory.rs new file mode 100644 index 0000000..c72e136 --- /dev/null +++ b/crates/borders-core/src/game/territory.rs @@ -0,0 +1,21 @@ +use glam::{IVec2, UVec2}; + +/// Convert 2D coordinates to a flat array index +pub fn get_idx>(pos: T, width: u32) -> usize { + let pos = pos.into(); + pos.x as usize + pos.y as usize * width as usize +} + +const CARDINAL_DIRECTIONS: [IVec2; 4] = [IVec2::new(0, 1), IVec2::new(1, 0), IVec2::new(0, -1), IVec2::new(-1, 0)]; + +/// Returns an iterator over the valid cardinal neighbors of a tile. +/// +/// Requires the width and height of the map to be passed in to ensure the neighbor is within bounds. +pub fn get_neighbors(pos: UVec2, width: u32, height: u32) -> impl Iterator { + let in_bounds = move |neighbor: IVec2| (0..width).contains(&(neighbor.x as u32)) && (0..height).contains(&(neighbor.y as u32)); + + CARDINAL_DIRECTIONS.into_iter().filter_map(move |dir| { + let neighbor = pos.as_ivec2().saturating_add(dir); + in_bounds(neighbor).then_some(neighbor.as_uvec2()) + }) +} diff --git a/crates/borders-core/src/game/territory_manager.rs b/crates/borders-core/src/game/territory_manager.rs new file mode 100644 index 0000000..4918ade --- /dev/null +++ b/crates/borders-core/src/game/territory_manager.rs @@ -0,0 +1,202 @@ +use crate::game::tile_ownership::{ENCODED_WATER, TileOwnership}; +use crate::game::tilemap::TileMap; +use crate::game::tilemap_changes::ChangeBuffer; + +/// Deprecated: Use TileOwnership::Water instead +/// Kept for backward compatibility during migration +pub const OWNER_WATER: u16 = ENCODED_WATER; + +/// Manages territory ownership for all tiles +pub struct TerritoryManager { + tile_owners: TileMap, + changes: ChangeBuffer, + /// Cached u16 representation for efficient serialization to frontend + u16_cache: Vec, + cache_dirty: bool, +} + +impl TerritoryManager { + /// Creates a new territory manager + pub fn new(width: u32, height: u32) -> Self { + let size = (width * height) as usize; + Self { tile_owners: TileMap::with_default(width, height, TileOwnership::Unclaimed), changes: ChangeBuffer::with_capacity((width * height / 100) as usize), u16_cache: vec![0; size], cache_dirty: true } + } + + /// Resets the territory manager + /// Should only be called when a new game is started + pub fn reset(&mut self, width: u32, height: u32, conquerable_tiles: &[bool]) { + self.tile_owners = TileMap::with_default(width, height, TileOwnership::Unclaimed); + self.changes.clear(); + + for (i, &conquerable) in conquerable_tiles.iter().enumerate() { + if !conquerable { + self.tile_owners[i] = TileOwnership::Water; + } + } + + let size = (width * height) as usize; + self.u16_cache.resize(size, 0); + self.cache_dirty = true; + } + + /// Checks if a tile is a border tile of the territory of its owner + /// A tile is a border tile if it is adjacent to a tile that is not owned by the same player + pub fn is_border(&self, tile: usize) -> bool { + let pos = self.tile_owners.index_to_pos(tile); + let owner = self.tile_owners[tile]; + + // Border if on map edge + if pos.x == 0 || pos.x == self.tile_owners.width() - 1 || pos.y == 0 || pos.y == self.tile_owners.height() - 1 { + return true; + } + + // Border if any neighbor has different owner + for neighbor_pos in self.tile_owners.neighbors(pos) { + if self.tile_owners[neighbor_pos] != owner { + return true; + } + } + + false + } + + /// Checks if a tile has an owner + pub fn has_owner(&self, tile: usize) -> bool { + self.tile_owners[tile].is_owned() + } + + /// Checks if a tile is owned by a specific player + pub fn is_owner(&self, tile: usize, owner: usize) -> bool { + self.tile_owners[tile].is_owned_by(owner as u16) + } + + /// Gets the owner of a tile as u16 (for compatibility) + /// Returns the encoded u16 value (nation_id, or ENCODED_UNCLAIMED/ENCODED_WATER) + pub fn get_owner(&self, tile: usize) -> u16 { + self.tile_owners[tile].to_u16() + } + + /// Gets the ownership enum for a tile + pub fn get_ownership(&self, tile: usize) -> TileOwnership { + self.tile_owners[tile] + } + + /// Checks if a tile is water + pub fn is_water(&self, tile: usize) -> bool { + self.tile_owners[tile].is_water() + } + + /// Conquers a tile for a player + /// If the tile is already owned by a player, that player will lose the tile + /// Only records a change if the owner actually changed + pub fn conquer(&mut self, tile: usize, owner: usize) -> u16 { + let previous_owner = self.tile_owners[tile]; + let new_ownership = TileOwnership::Owned(owner as u16); + + // Only update and track change if the owner actually changed + if previous_owner != new_ownership { + self.tile_owners[tile] = new_ownership; + self.changes.push(tile); + self.cache_dirty = true; + } + + previous_owner.to_u16() + } + + /// Clears a tile (removes ownership) + pub fn clear(&mut self, tile: usize) -> Option { + let ownership = self.tile_owners[tile]; + if ownership.is_owned() { + self.tile_owners[tile] = TileOwnership::Unclaimed; + self.changes.push(tile); + self.cache_dirty = true; + ownership.nation_id() + } else { + None + } + } + + /// Get width of the map + pub fn width(&self) -> u32 { + self.tile_owners.width() + } + + /// Get height of the map + pub fn height(&self) -> u32 { + self.tile_owners.height() + } + + /// Returns a reference to the underlying tile ownership data as a slice of enums + pub fn as_slice(&self) -> &[TileOwnership] { + self.tile_owners.as_slice() + } + + /// Returns the tile ownership data as u16 values for frontend serialization + /// This is cached and only recomputed when ownership changes + pub fn as_u16_slice(&mut self) -> &[u16] { + if self.cache_dirty { + for (i, ownership) in self.tile_owners.as_slice().iter().enumerate() { + self.u16_cache[i] = ownership.to_u16(); + } + self.cache_dirty = false; + } + &self.u16_cache + } + + /// Converts tile ownership to a Vec for serialization (immutable) + /// Use this when you need a Vec for creating Arc<[u16]> + pub fn to_u16_vec(&self) -> Vec { + self.tile_owners.as_slice().iter().map(|ownership| ownership.to_u16()).collect() + } + + /// Returns the number of tiles in the map + pub fn len(&self) -> usize { + self.tile_owners.len() + } + + /// Returns true if the map has no tiles + pub fn is_empty(&self) -> bool { + self.tile_owners.len() == 0 + } + + /// Returns an iterator over changed tile indices without consuming them + /// Use this to read changes without clearing the buffer + pub fn iter_changes(&self) -> impl Iterator + '_ { + self.changes.iter() + } + + /// Drains all changed tile indices, returning an iterator and clearing the change buffer + pub fn drain_changes(&mut self) -> impl Iterator + '_ { + self.changes.drain() + } + + /// Returns true if any territory changes have been recorded since last drain + pub fn has_changes(&self) -> bool { + self.changes.has_changes() + } + + /// Clears all tracked changes without returning them + pub fn clear_changes(&mut self) { + self.changes.clear() + } + + /// Calls a closure for each neighbor using tile indices + pub fn on_neighbor_indices(&self, index: usize, closure: F) + where + F: FnMut(usize), + { + self.tile_owners.on_neighbor_indices(index, closure) + } + + /// Checks if any neighbor has a different owner than the specified owner + /// This is optimized for border detection with early exit + pub fn any_neighbor_has_different_owner(&self, tile: usize, owner: u16) -> bool { + let mut has_different = false; + self.on_neighbor_indices(tile, |neighbor| { + if !has_different && self.tile_owners[neighbor].to_u16() != owner { + has_different = true; + } + }); + has_different + } +} diff --git a/crates/borders-core/src/game/tile_ownership.rs b/crates/borders-core/src/game/tile_ownership.rs new file mode 100644 index 0000000..d216410 --- /dev/null +++ b/crates/borders-core/src/game/tile_ownership.rs @@ -0,0 +1,159 @@ +//! Tile ownership representation +//! +//! This module defines how tiles are owned and what their terrain type is. +//! It separates the concept of "who owns this tile" from "what type of terrain is this". +//! +//! ## Encoding +//! +//! For frontend serialization, TileOwnership is encoded as a u16: +//! - 0-65533: Nation IDs (supports 400+ nations) +//! - 65534: Unclaimed land +//! - 65535: Water (unconquerable terrain) + +use serde::{Deserialize, Serialize}; + +/// Encoded value for unclaimed land in u16 representation +pub const ENCODED_UNCLAIMED: u16 = 65534; + +/// Encoded value for water in u16 representation +/// Kept at 65535 for backward compatibility with OWNER_WATER +pub const ENCODED_WATER: u16 = 65535; + +/// Represents the ownership/state of a single tile +/// +/// This enum clearly separates nation ownership from terrain type, +/// allowing nation ID 0 to be valid without confusion with wilderness. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Default)] +pub enum TileOwnership { + /// Owned by a nation (0-65533, supporting 65,534 possible nations) + Owned(u16), + /// Unclaimed but conquerable land + #[default] + Unclaimed, + /// Water (unconquerable terrain) + Water, +} + +impl TileOwnership { + /// Convert to u16 for frontend serialization + pub fn to_u16(self) -> u16 { + match self { + TileOwnership::Owned(nation_id) => nation_id, + TileOwnership::Unclaimed => ENCODED_UNCLAIMED, + TileOwnership::Water => ENCODED_WATER, + } + } + + /// Convert from u16 (frontend serialization format) + pub fn from_u16(value: u16) -> Self { + match value { + ENCODED_WATER => TileOwnership::Water, + ENCODED_UNCLAIMED => TileOwnership::Unclaimed, + nation_id => TileOwnership::Owned(nation_id), + } + } + + /// Check if this tile is owned by any nation + pub fn is_owned(self) -> bool { + matches!(self, TileOwnership::Owned(_)) + } + + /// Check if this tile is unclaimed land + pub fn is_unclaimed(self) -> bool { + matches!(self, TileOwnership::Unclaimed) + } + + /// Check if this tile is water + pub fn is_water(self) -> bool { + matches!(self, TileOwnership::Water) + } + + /// Get the nation ID if this tile is owned, otherwise None + pub fn nation_id(self) -> Option { + match self { + TileOwnership::Owned(id) => Some(id), + _ => None, + } + } + + /// Check if this tile is owned by a specific nation + pub fn is_owned_by(self, nation_id: u16) -> bool { + matches!(self, TileOwnership::Owned(id) if id == nation_id) + } +} + +impl From for TileOwnership { + fn from(value: u16) -> Self { + Self::from_u16(value) + } +} + +impl From for u16 { + fn from(ownership: TileOwnership) -> Self { + ownership.to_u16() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encoding_nation_ids() { + for nation_id in [0, 1, 100, 1000, 65533] { + let ownership = TileOwnership::Owned(nation_id); + let encoded = ownership.to_u16(); + assert_eq!(encoded, nation_id); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + } + + #[test] + fn test_encoding_unclaimed() { + let ownership = TileOwnership::Unclaimed; + let encoded = ownership.to_u16(); + assert_eq!(encoded, ENCODED_UNCLAIMED); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + + #[test] + fn test_encoding_water() { + let ownership = TileOwnership::Water; + let encoded = ownership.to_u16(); + assert_eq!(encoded, ENCODED_WATER); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + + #[test] + fn test_is_owned() { + assert!(TileOwnership::Owned(0).is_owned()); + assert!(TileOwnership::Owned(100).is_owned()); + assert!(!TileOwnership::Unclaimed.is_owned()); + assert!(!TileOwnership::Water.is_owned()); + } + + #[test] + fn test_is_owned_by() { + assert!(TileOwnership::Owned(5).is_owned_by(5)); + assert!(!TileOwnership::Owned(5).is_owned_by(6)); + assert!(!TileOwnership::Unclaimed.is_owned_by(5)); + assert!(!TileOwnership::Water.is_owned_by(5)); + } + + #[test] + fn test_nation_id() { + assert_eq!(TileOwnership::Owned(42).nation_id(), Some(42)); + assert_eq!(TileOwnership::Unclaimed.nation_id(), None); + assert_eq!(TileOwnership::Water.nation_id(), None); + } + + #[test] + fn test_nation_zero_is_valid() { + let ownership = TileOwnership::Owned(0); + assert!(ownership.is_owned()); + assert!(ownership.is_owned_by(0)); + assert_eq!(ownership.nation_id(), Some(0)); + assert_ne!(ownership, TileOwnership::Unclaimed); + assert_ne!(ownership, TileOwnership::Water); + } +} diff --git a/crates/borders-core/src/game/tilemap.rs b/crates/borders-core/src/game/tilemap.rs new file mode 100644 index 0000000..2a767bc --- /dev/null +++ b/crates/borders-core/src/game/tilemap.rs @@ -0,0 +1,441 @@ +use glam::UVec2; +use std::ops::{Index, IndexMut}; + +/// A 2D grid-based map structure optimized for tile-based games. +/// +/// Provides efficient access to tiles using 2D coordinates (UVec2) while maintaining +/// cache-friendly contiguous memory layout. Supports generic tile types that implement Copy. +/// +/// # Type Parameters +/// * `T` - The tile value type. Must implement `Copy` for efficient access. +/// +/// # Examples +/// ``` +/// use glam::UVec2; +/// use borders_core::game::TileMap; +/// +/// let mut map = TileMap::::new(10, 10); +/// map[UVec2::new(5, 5)] = 42; +/// assert_eq!(map[UVec2::new(5, 5)], 42); +/// ``` +#[derive(Clone, Debug)] +pub struct TileMap { + tiles: Box<[T]>, + width: u32, + height: u32, +} + +impl TileMap { + /// Creates a new TileMap with the specified dimensions and default value. + /// + /// # Arguments + /// * `width` - The width of the map in tiles + /// * `height` - The height of the map in tiles + /// * `default` - The default value to initialize all tiles with + pub fn with_default(width: u32, height: u32, default: T) -> Self { + let capacity = (width * height) as usize; + let tiles = vec![default; capacity].into_boxed_slice(); + Self { tiles, width, height } + } + + /// Creates a TileMap from an existing vector of tile data. + /// + /// # Arguments + /// * `width` - The width of the map in tiles + /// * `height` - The height of the map in tiles + /// * `data` - Vector containing tile data in row-major order + /// + /// # Panics + /// Panics if `data.len() != width * height` + pub fn from_vec(width: u32, height: u32, data: Vec) -> Self { + assert_eq!(data.len(), (width * height) as usize, "Data length must match width * height"); + Self { tiles: data.into_boxed_slice(), width, height } + } + + /// Converts the position to a flat array index. + /// + /// # Safety + /// Debug builds will assert that the position is in bounds. + /// Release builds skip the check for performance. + #[inline] + pub fn pos_to_index(&self, pos: UVec2) -> usize { + debug_assert!(pos.x < self.width && pos.y < self.height); + (pos.y * self.width + pos.x) as usize + } + + /// Converts a flat array index to a 2D position. + #[inline] + pub fn index_to_pos(&self, index: usize) -> UVec2 { + debug_assert!(index < self.tiles.len()); + UVec2::new((index as u32) % self.width, (index as u32) / self.width) + } + + /// Checks if a position is within the map bounds. + #[inline] + pub fn in_bounds(&self, pos: UVec2) -> bool { + pos.x < self.width && pos.y < self.height + } + + /// Gets the tile value at the specified position. + /// + /// Returns `None` if the position is out of bounds. + pub fn get(&self, pos: UVec2) -> Option { + if self.in_bounds(pos) { Some(self.tiles[self.pos_to_index(pos)]) } else { None } + } + + /// Sets the tile value at the specified position. + /// + /// Returns `true` if the position was in bounds and the value was set, + /// `false` otherwise. + pub fn set(&mut self, pos: UVec2, tile: T) -> bool { + if self.in_bounds(pos) { + let idx = self.pos_to_index(pos); + self.tiles[idx] = tile; + true + } else { + false + } + } + + /// Returns the width of the map. + #[inline] + pub fn width(&self) -> u32 { + self.width + } + + /// Returns the height of the map. + #[inline] + pub fn height(&self) -> u32 { + self.height + } + + /// Returns the total number of tiles in the map. + #[inline] + pub fn len(&self) -> usize { + self.tiles.len() + } + + /// Returns `true` if the map contains no tiles. + #[inline] + pub fn is_empty(&self) -> bool { + self.tiles.is_empty() + } + + /// Returns an iterator over all valid cardinal neighbors of a position. + /// + /// Yields positions for up, down, left, and right neighbors that are within bounds. + pub fn neighbors(&self, pos: UVec2) -> impl Iterator + '_ { + const CARDINAL_DIRECTIONS: [(i32, i32); 4] = [(0, 1), (1, 0), (0, -1), (-1, 0)]; + + let pos_i32 = (pos.x as i32, pos.y as i32); + let width = self.width; + let height = self.height; + + CARDINAL_DIRECTIONS.iter().filter_map(move |(dx, dy)| { + let nx = pos_i32.0 + dx; + let ny = pos_i32.1 + dy; + if nx >= 0 && ny >= 0 && nx < width as i32 && ny < height as i32 { Some(UVec2::new(nx as u32, ny as u32)) } else { None } + }) + } + + /// Calls a closure for each valid cardinal neighbor of a position. + /// + /// This is more efficient than using the `neighbors()` iterator when you don't + /// need to collect the neighbors. + pub fn on_neighbors(&self, pos: UVec2, mut closure: F) + where + F: FnMut(UVec2), + { + if pos.x > 0 { + closure(UVec2::new(pos.x - 1, pos.y)); + } + if pos.x < self.width - 1 { + closure(UVec2::new(pos.x + 1, pos.y)); + } + if pos.y > 0 { + closure(UVec2::new(pos.x, pos.y - 1)); + } + if pos.y < self.height - 1 { + closure(UVec2::new(pos.x, pos.y + 1)); + } + } + + /// Calls a closure for each neighbor using tile indices instead of positions. + /// + /// This is useful when working with systems that still use raw indices. + pub fn on_neighbor_indices(&self, index: usize, mut closure: F) + where + F: FnMut(usize), + { + let width = self.width as usize; + let height = self.height as usize; + let x = index % width; + let y = index / width; + + if x > 0 { + closure(index - 1); + } + if x < width - 1 { + closure(index + 1); + } + if y > 0 { + closure(index - width); + } + if y < height - 1 { + closure(index + width); + } + } + + /// Returns an iterator over all positions and their tile values. + pub fn iter(&self) -> impl Iterator + '_ { + (0..self.height).flat_map(move |y| { + (0..self.width).map(move |x| { + let pos = UVec2::new(x, y); + (pos, self[pos]) + }) + }) + } + + /// Returns an iterator over just the tile values. + pub fn iter_values(&self) -> impl Iterator + '_ { + self.tiles.iter().copied() + } + + /// Returns an iterator over all positions in the map. + pub fn positions(&self) -> impl Iterator + '_ { + (0..self.height).flat_map(move |y| (0..self.width).map(move |x| UVec2::new(x, y))) + } + + /// Returns an iterator over tile indices, positions, and values. + pub fn enumerate(&self) -> impl Iterator + '_ { + self.tiles.iter().enumerate().map(move |(idx, &value)| { + let pos = self.index_to_pos(idx); + (idx, pos, value) + }) + } + + /// Returns a reference to the underlying tile data as a slice. + pub fn as_slice(&self) -> &[T] { + &self.tiles + } + + /// Returns a mutable reference to the underlying tile data as a slice. + pub fn as_mut_slice(&mut self) -> &mut [T] { + &mut self.tiles + } +} + +impl TileMap { + /// Creates a new TileMap with the specified dimensions, using T::default() for initialization. + pub fn new(width: u32, height: u32) -> Self { + Self::with_default(width, height, T::default()) + } +} + +impl Index for TileMap { + type Output = T; + + #[inline] + fn index(&self, pos: UVec2) -> &Self::Output { + &self.tiles[self.pos_to_index(pos)] + } +} + +impl IndexMut for TileMap { + #[inline] + fn index_mut(&mut self, pos: UVec2) -> &mut Self::Output { + let idx = self.pos_to_index(pos); + &mut self.tiles[idx] + } +} + +impl Index for TileMap { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.tiles[index] + } +} + +impl IndexMut for TileMap { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.tiles[index] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_with_default() { + let map = TileMap::::with_default(10, 10, 42); + assert_eq!(map.width(), 10); + assert_eq!(map.height(), 10); + assert_eq!(map[UVec2::new(0, 0)], 42); + assert_eq!(map[UVec2::new(9, 9)], 42); + } + + #[test] + fn test_from_vec() { + let data = vec![1u8, 2, 3, 4]; + let map = TileMap::from_vec(2, 2, data); + assert_eq!(map[UVec2::new(0, 0)], 1); + assert_eq!(map[UVec2::new(1, 0)], 2); + assert_eq!(map[UVec2::new(0, 1)], 3); + assert_eq!(map[UVec2::new(1, 1)], 4); + } + + #[test] + fn test_pos_to_index() { + let map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.pos_to_index(UVec2::new(0, 0)), 0); + assert_eq!(map.pos_to_index(UVec2::new(5, 0)), 5); + assert_eq!(map.pos_to_index(UVec2::new(0, 1)), 10); + assert_eq!(map.pos_to_index(UVec2::new(3, 2)), 23); + } + + #[test] + fn test_index_to_pos() { + let map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.index_to_pos(0), UVec2::new(0, 0)); + assert_eq!(map.index_to_pos(5), UVec2::new(5, 0)); + assert_eq!(map.index_to_pos(10), UVec2::new(0, 1)); + assert_eq!(map.index_to_pos(23), UVec2::new(3, 2)); + } + + #[test] + fn test_in_bounds() { + let map = TileMap::::with_default(10, 10, 0); + assert!(map.in_bounds(UVec2::new(0, 0))); + assert!(map.in_bounds(UVec2::new(9, 9))); + assert!(!map.in_bounds(UVec2::new(10, 0))); + assert!(!map.in_bounds(UVec2::new(0, 10))); + } + + #[test] + fn test_get_set() { + let mut map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.get(UVec2::new(5, 5)), Some(0)); + + assert!(map.set(UVec2::new(5, 5), 42)); + assert_eq!(map.get(UVec2::new(5, 5)), Some(42)); + + assert!(!map.set(UVec2::new(10, 10), 99)); + assert_eq!(map.get(UVec2::new(10, 10)), None); + } + + #[test] + fn test_index_operators() { + let mut map = TileMap::::with_default(10, 10, 0); + map[UVec2::new(5, 5)] = 42; + assert_eq!(map[UVec2::new(5, 5)], 42); + } + + #[test] + fn test_index_by_usize() { + let mut map = TileMap::::with_default(10, 10, 0); + map[23] = 42; + assert_eq!(map[23], 42); + assert_eq!(map[UVec2::new(3, 2)], 42); + } + + #[test] + fn test_neighbors_center() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(UVec2::new(5, 5)).collect(); + assert_eq!(neighbors.len(), 4); + assert!(neighbors.contains(&UVec2::new(5, 6))); + assert!(neighbors.contains(&UVec2::new(6, 5))); + assert!(neighbors.contains(&UVec2::new(5, 4))); + assert!(neighbors.contains(&UVec2::new(4, 5))); + } + + #[test] + fn test_neighbors_corner() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(UVec2::new(0, 0)).collect(); + assert_eq!(neighbors.len(), 2); + assert!(neighbors.contains(&UVec2::new(1, 0))); + assert!(neighbors.contains(&UVec2::new(0, 1))); + } + + #[test] + fn test_neighbors_edge() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(UVec2::new(0, 5)).collect(); + assert_eq!(neighbors.len(), 3); + assert!(neighbors.contains(&UVec2::new(0, 6))); + assert!(neighbors.contains(&UVec2::new(1, 5))); + assert!(neighbors.contains(&UVec2::new(0, 4))); + } + + #[test] + fn test_on_neighbors() { + let map = TileMap::::with_default(10, 10, 0); + let mut count = 0; + map.on_neighbors(UVec2::new(5, 5), |_| count += 1); + assert_eq!(count, 4); + } + + #[test] + fn test_on_neighbor_indices() { + let map = TileMap::::with_default(10, 10, 0); + let center_idx = map.pos_to_index(UVec2::new(5, 5)); + let mut count = 0; + map.on_neighbor_indices(center_idx, |_| count += 1); + assert_eq!(count, 4); + } + + #[test] + fn test_iter() { + let map = TileMap::::with_default(2, 2, 0); + let positions: Vec<_> = map.iter().map(|(pos, _)| pos).collect(); + assert_eq!(positions.len(), 4); + assert!(positions.contains(&UVec2::new(0, 0))); + assert!(positions.contains(&UVec2::new(1, 1))); + } + + #[test] + fn test_iter_values() { + let map = TileMap::::with_default(2, 2, 42); + let values: Vec<_> = map.iter_values().collect(); + assert_eq!(values, vec![42, 42, 42, 42]); + } + + #[test] + fn test_positions() { + let map = TileMap::::with_default(2, 2, 0); + let positions: Vec<_> = map.positions().collect(); + assert_eq!(positions.len(), 4); + assert_eq!(positions[0], UVec2::new(0, 0)); + assert_eq!(positions[3], UVec2::new(1, 1)); + } + + #[test] + fn test_enumerate() { + let mut map = TileMap::::with_default(2, 2, 0); + map[UVec2::new(1, 1)] = 42; + let entries: Vec<_> = map.enumerate().collect(); + assert_eq!(entries.len(), 4); + assert_eq!(entries[3], (3, UVec2::new(1, 1), 42)); + } + + #[test] + fn test_generic_u16() { + let mut map = TileMap::::with_default(5, 5, 0); + assert_eq!(map[UVec2::new(0, 0)], 0); + map[UVec2::new(2, 2)] = 65535; + assert_eq!(map[UVec2::new(2, 2)], 65535); + } + + #[test] + fn test_generic_f32() { + let mut map = TileMap::::with_default(5, 5, 1.5); + assert_eq!(map[UVec2::new(0, 0)], 1.5); + map[UVec2::new(2, 2)] = 2.7; + assert_eq!(map[UVec2::new(2, 2)], 2.7); + } +} diff --git a/crates/borders-core/src/game/tilemap_changes.rs b/crates/borders-core/src/game/tilemap_changes.rs new file mode 100644 index 0000000..dfddc6a --- /dev/null +++ b/crates/borders-core/src/game/tilemap_changes.rs @@ -0,0 +1,179 @@ +/// Lightweight change tracking buffer for tile mutations. +/// +/// Stores only the indices of changed tiles, avoiding allocations in the hot path +/// by reusing Vec capacity across frames. This enables efficient delta updates +/// for GPU rendering and network synchronization. +/// +/// # Design +/// - Records tile index changes as they occur +/// - Reuses Vec capacity to avoid allocations +/// - O(1) push, O(changes) iteration +/// - Optional: can be cleared/ignored when tracking not needed +/// +/// # Example +/// ``` +/// use borders_core::game::ChangeBuffer; +/// +/// let mut changes = ChangeBuffer::new(); +/// changes.push(10); +/// changes.push(25); +/// assert_eq!(changes.len(), 2); +/// +/// let indices: Vec<_> = changes.drain().collect(); +/// assert_eq!(indices, vec![10, 25]); +/// assert_eq!(changes.len(), 0); +/// ``` +#[derive(Debug, Clone)] +pub struct ChangeBuffer { + changed_indices: Vec, +} + +impl ChangeBuffer { + /// Creates a new empty ChangeBuffer. + pub fn new() -> Self { + Self { changed_indices: Vec::new() } + } + + /// Creates a new ChangeBuffer with pre-allocated capacity. + /// + /// Use this when you know the approximate number of changes to avoid reallocations. + pub fn with_capacity(capacity: usize) -> Self { + Self { changed_indices: Vec::with_capacity(capacity) } + } + + /// Records a tile index as changed. + /// + /// Does not check for duplicates - the same index can be pushed multiple times. + /// Consumers should handle deduplication if needed. + #[inline] + pub fn push(&mut self, index: usize) { + self.changed_indices.push(index); + } + + /// Returns an iterator over changed indices without consuming them. + /// + /// Use this when you need to read changes without clearing the buffer. + /// The buffer will still contain all changes after iteration. + pub fn iter(&self) -> impl Iterator + '_ { + self.changed_indices.iter().copied() + } + + /// Drains all changed indices, returning an iterator and clearing the buffer. + /// + /// The buffer retains its capacity for reuse. + pub fn drain(&mut self) -> impl Iterator + '_ { + self.changed_indices.drain(..) + } + + /// Clears all tracked changes without returning them. + /// + /// The buffer retains its capacity for reuse. + pub fn clear(&mut self) { + self.changed_indices.clear(); + } + + /// Returns true if any changes have been recorded. + #[inline] + pub fn has_changes(&self) -> bool { + !self.changed_indices.is_empty() + } + + /// Returns the number of changes recorded. + /// + /// Note: This may include duplicate indices if the same tile was changed multiple times. + #[inline] + pub fn len(&self) -> usize { + self.changed_indices.len() + } + + /// Returns true if no changes have been recorded. + #[inline] + pub fn is_empty(&self) -> bool { + self.changed_indices.is_empty() + } + + /// Returns the current capacity of the internal buffer. + #[inline] + pub fn capacity(&self) -> usize { + self.changed_indices.capacity() + } +} + +impl Default for ChangeBuffer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let buffer = ChangeBuffer::new(); + assert!(buffer.is_empty()); + assert_eq!(buffer.len(), 0); + } + + #[test] + fn test_with_capacity() { + let buffer = ChangeBuffer::with_capacity(100); + assert_eq!(buffer.capacity(), 100); + assert!(buffer.is_empty()); + } + + #[test] + fn test_push_and_drain() { + let mut buffer = ChangeBuffer::new(); + buffer.push(10); + buffer.push(25); + buffer.push(42); + + assert_eq!(buffer.len(), 3); + assert!(buffer.has_changes()); + + let changes: Vec<_> = buffer.drain().collect(); + assert_eq!(changes, vec![10, 25, 42]); + assert!(buffer.is_empty()); + } + + #[test] + fn test_clear() { + let mut buffer = ChangeBuffer::new(); + buffer.push(1); + buffer.push(2); + buffer.push(3); + + assert_eq!(buffer.len(), 3); + buffer.clear(); + assert_eq!(buffer.len(), 0); + assert!(buffer.is_empty()); + } + + #[test] + fn test_duplicate_indices() { + let mut buffer = ChangeBuffer::new(); + buffer.push(10); + buffer.push(10); + buffer.push(10); + + assert_eq!(buffer.len(), 3); // Does not deduplicate + let changes: Vec<_> = buffer.drain().collect(); + assert_eq!(changes, vec![10, 10, 10]); + } + + #[test] + fn test_capacity_retained_after_drain() { + let mut buffer = ChangeBuffer::with_capacity(100); + buffer.push(1); + buffer.push(2); + + let initial_capacity = buffer.capacity(); + let _: Vec<_> = buffer.drain().collect(); + + // Capacity should be retained after drain + assert!(buffer.capacity() >= initial_capacity); + assert!(buffer.is_empty()); + } +} diff --git a/crates/borders-core/src/game/turn.rs b/crates/borders-core/src/game/turn.rs new file mode 100644 index 0000000..fe6f7d3 --- /dev/null +++ b/crates/borders-core/src/game/turn.rs @@ -0,0 +1,34 @@ +use bevy_ecs::prelude::*; + +use crate::networking::Turn; + +/// Resource containing the current turn data +/// Updated once per turn (10 TPS), provides turn context to all gameplay systems +#[derive(Resource)] +pub struct CurrentTurn { + pub turn: Turn, + /// Flag indicating if this turn has been processed by gameplay systems + /// Set to false when turn arrives, set to true after all systems run + pub processed: bool, +} + +impl CurrentTurn { + pub fn new(turn: Turn) -> Self { + Self { turn, processed: false } + } + + /// Mark turn as processed + pub fn mark_processed(&mut self) { + self.processed = true; + } + + /// Check if turn is ready to process (not yet processed) + pub fn is_ready(&self) -> bool { + !self.processed + } +} + +/// Run condition: only run when a turn is ready to process +pub fn turn_is_ready(current_turn: Option>) -> bool { + current_turn.is_some_and(|ct| ct.is_ready()) +} diff --git a/crates/borders-core/src/game/utils.rs b/crates/borders-core/src/game/utils.rs new file mode 100644 index 0000000..d52277b --- /dev/null +++ b/crates/borders-core/src/game/utils.rs @@ -0,0 +1,66 @@ +/// Utility functions for tile operations +/// +/// Call a closure for each neighbor of a tile +/// +/// Handles boundary checks for the 4-connected grid (up, down, left, right) +pub fn for_each_neighbor(tile: usize, width: u32, height: u32, mut closure: F) +where + F: FnMut(usize), +{ + let x = tile as u32 % width; + let y = tile as u32 / width; + + // Left neighbor + if x > 0 { + closure(tile - 1); + } + // Right neighbor + if x < width - 1 { + closure(tile + 1); + } + // Top neighbor + if y > 0 { + closure(tile - width as usize); + } + // Bottom neighbor + if y < height - 1 { + closure(tile + width as usize); + } +} + +/// Check if a tile has at least one neighbor matching the given predicate +pub fn has_neighbor_owned_by(tile: usize, width: u32, height: u32, is_owner: impl Fn(usize) -> bool) -> bool { + let mut has_neighbor = false; + for_each_neighbor(tile, width, height, |neighbor| { + if is_owner(neighbor) { + has_neighbor = true; + } + }); + has_neighbor +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_corner_tile_neighbors() { + let mut neighbors = Vec::new(); + for_each_neighbor(0, 10, 10, |n| neighbors.push(n)); + assert_eq!(neighbors, vec![1, 10]); // Right and down only + } + + #[test] + fn test_edge_tile_neighbors() { + let mut neighbors = Vec::new(); + for_each_neighbor(5, 10, 10, |n| neighbors.push(n)); + assert_eq!(neighbors, vec![4, 6, 15]); // Left, right, and down + } + + #[test] + fn test_center_tile_neighbors() { + let mut neighbors = Vec::new(); + for_each_neighbor(55, 10, 10, |n| neighbors.push(n)); + assert_eq!(neighbors, vec![54, 56, 45, 65]); // All 4 directions + } +} diff --git a/crates/borders-core/src/lib.rs b/crates/borders-core/src/lib.rs new file mode 100644 index 0000000..9959e39 --- /dev/null +++ b/crates/borders-core/src/lib.rs @@ -0,0 +1,18 @@ +pub mod app; +pub mod build_info; +pub mod constants; +pub mod game; +pub mod networking; +pub mod platform; +pub mod plugin; +pub mod telemetry; +pub mod time; +#[cfg(feature = "ui")] +pub mod ui; + +pub use constants::*; +pub use game::*; +pub use networking::*; +pub use plugin::*; +#[cfg(feature = "ui")] +pub use ui::{LastAttacksDigest, LastLeaderboardDigest, NationHighlightState, input}; diff --git a/crates/borders-core/src/networking/client.rs b/crates/borders-core/src/networking/client.rs new file mode 100644 index 0000000..8127dd2 --- /dev/null +++ b/crates/borders-core/src/networking/client.rs @@ -0,0 +1,103 @@ +use crate::networking::{GameView, Intent, IntentEvent, ProcessTurnEvent, protocol::NetMessage}; +use bevy_ecs::prelude::*; +use flume::{Receiver, Sender}; +use tracing::{debug, error, info, warn}; + +#[derive(Resource)] +pub struct ClientConnection { + pub intent_tx: Sender, + pub game_view_rx: Receiver, +} + +#[derive(Resource)] +pub struct RemoteClientConnection { + pub intent_tx: Sender, + pub net_message_rx: Receiver, + pub player_id: Option, +} + +pub fn receive_game_view_system(client: Res, mut game_view: ResMut) { + while let Ok(new_view) = client.game_view_rx.try_recv() { + *game_view = new_view; + } +} + +pub fn send_intent_system(mut intent_events: MessageReader, client: Res) { + for event in intent_events.read() { + debug!("Sending intent to local server: {:?}", event.0); + if let Err(e) = client.intent_tx.try_send(event.0.clone()) { + error!("Failed to send intent: {:?}", e); + } + } +} + +/// System for remote clients to handle NetMessage protocol +pub fn receive_net_message_system(remote_client: Res, mut process_turn_writer: MessageWriter, mut spawn_config_writer: MessageWriter) { + let mut message_count = 0; + while let Ok(message) = remote_client.net_message_rx.try_recv() { + message_count += 1; + match message { + NetMessage::ServerConfig { player_id } => { + info!("Received server config: player_id={}", player_id); + // Store player_id in the resource (would need to make it mutable) + } + NetMessage::Turn { turn, intents } => { + info!("Received turn {} with {} intents", turn, intents.len()); + // Convert to ProcessTurnEvent + let turn_event = ProcessTurnEvent(crate::networking::Turn { turn_number: turn, intents }); + process_turn_writer.write(turn_event); + } + NetMessage::Intent(_) => { + warn!("Received Intent message on client side"); + } + NetMessage::SpawnConfiguration { spawns } => { + info!("Received spawn configuration with {} spawns", spawns.len()); + spawn_config_writer.write(crate::networking::SpawnConfigEvent(spawns)); + } + } + } + + if message_count > 0 { + let _span = tracing::debug_span!("receive_net_messages", message_count).entered(); + } +} + +/// System for remote clients to send intents as NetMessage +pub fn send_net_intent_system(mut intent_events: MessageReader, remote_client: Res) { + let mut intent_count = 0; + for event in intent_events.read() { + intent_count += 1; + let net_message = NetMessage::Intent(event.0.clone()); + if let Err(e) = remote_client.intent_tx.try_send(net_message) { + error!("Failed to send net intent: {:?}", e); + } + } + + if intent_count > 0 { + let _span = tracing::debug_span!("send_net_intents", intent_count).entered(); + } +} + +/// System to handle spawn configuration updates from server +/// Updates local SpawnManager with remote player spawn positions +pub fn handle_spawn_config_system(mut spawn_config_events: MessageReader, mut spawn_manager: Option>, game_instance: Option>) { + for event in spawn_config_events.read() { + let Some(ref mut spawn_mgr) = spawn_manager else { + continue; + }; + let Some(ref game_inst) = game_instance else { + continue; + }; + + // Update player spawns from server + spawn_mgr.player_spawns.clear(); + for (&player_id, &tile_index) in &event.0 { + spawn_mgr.player_spawns.push((player_id as usize, tile_index as usize)); + } + + // Recalculate bot spawns based on updated player positions + spawn_mgr.current_bot_spawns = game_inst.bot_manager.recalculate_spawns_with_players(spawn_mgr.initial_bot_spawns.clone(), &spawn_mgr.player_spawns, &game_inst.territory_manager, spawn_mgr.rng_seed); + + info!("Updated spawn manager with {} player spawns from server", spawn_mgr.player_spawns.len()); + } +} diff --git a/crates/borders-core/src/networking/coordinator.rs b/crates/borders-core/src/networking/coordinator.rs new file mode 100644 index 0000000..466569e --- /dev/null +++ b/crates/borders-core/src/networking/coordinator.rs @@ -0,0 +1,189 @@ +use crate::time::Time; +use bevy_ecs::prelude::*; +use tracing::{debug, info, trace, warn}; + +use crate::constants::{SPAWN_TIMEOUT_SECS, TICK_INTERVAL}; +use crate::networking::{Intent, ProcessTurnEvent, Turn}; +use flume::{Receiver, Sender}; +use std::collections::HashMap; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + +#[derive(Resource)] +pub struct IntentReceiver { + pub intent_rx: Receiver, +} + +#[derive(Resource)] +pub struct TurnReceiver { + pub turn_rx: Receiver, +} + +/// Local turn server control handle +#[derive(Resource, Clone)] +pub struct LocalTurnServerHandle { + pub paused: Arc, + pub running: Arc, +} + +impl LocalTurnServerHandle { + pub fn pause(&self) { + self.paused.store(true, Ordering::SeqCst); + } + + pub fn resume(&self) { + self.paused.store(false, Ordering::SeqCst); + } + + pub fn stop(&self) { + self.running.store(false, Ordering::SeqCst); + } + + pub fn is_paused(&self) -> bool { + self.paused.load(Ordering::SeqCst) + } + + pub fn is_running(&self) -> bool { + self.running.load(Ordering::SeqCst) + } +} + +/// Spawn timeout duration for local games (milliseconds) +const SPAWN_TIMEOUT_MS: u64 = (SPAWN_TIMEOUT_SECS * 1000.0) as u64; + +/// Resource to track turn generation state +#[derive(Resource)] +pub struct TurnGenerator { + pub turn_number: u64, + pub accumulated_time: f64, // milliseconds + pub turn_tx: Sender, + // Spawn phase tracking + pub spawn_config: HashMap, + pub spawn_timeout_accumulated: Option, // milliseconds since first spawn + pub game_started: bool, +} + +/// System to generate turns using Bevy's Update loop +pub fn generate_turns_system(mut generator: ResMut, server_handle: Res, intent_receiver: Res, mut spawn_manager: Option>, game_instance: Option>, time: Res

5l#v3A#xI3*z~1~fvo9Q(N^(==!|_FZ z*duZ=+M1~)8E|otX8KNZlr?qels#x_1Xq@9IIw~@9uAREJVH)Xw^}UclF6327}E42 zT)E&?U%TK?(+K7%R!`H5oX0i)4Qn5??Iw3p5J~6_u+aWehY{DSn}3V2p$bgjnAu?o)v@iC254fXeMv50$9YrpU`N?u@QIWs)T?SP|fa}(|9 zqAX+!7`cx=4)cCBg5h~pu(?@9`)aCr#oyz$ld=#RFxYCNZCZls@4v2~*e-t6PEVvV z&bbK3b3wt(Coc!ufAbXXC<**#HQ%J9k`New6iG<5RjtO4XVO?dCvwxD{kJ#tfQr(X zg^NTwF-FwAeS_{V4bfel8l`~NbfrTR2s!G>WduFWxH(t~aK4q=6rEE^$+Uox>gJO2 z{L<;6Q6nHa5#ZEM>H58not!)z(6*_=^~8}jWf*IG$AUKVWOZ4?)GfF z+BM#*wKKmLFD7E~W3U!$IVm$k_k1f&Kz6WV8@55P?r~bcg-Za-!rvW?ns&)KOGT2~ zlkAyqhQj=P$Eg3w#K~}zH@J5bo-BfHjInKSz$@?+Z)NPD4pHj^_Qxmi`UqoTy=`sV zLVxrXGuBr=QRm|}wg75yetQQK4fY3#P_~J}zEfPnb2C4Wo!E(d*(cA;b?7$g2in<( zPn)ghX}nzJPmb6(3Dpeg_GW~Hc}Lt=lgsSZz z!5QXyz7KaR;D`3Ee}d`af{H>WWZ|Io1QI3~4Ll_`g1(cRnhLK73Ro)7zPCd={1W2x zRp%Xlvv4>!<2@}$hz|!V{T}_eHx2xkLl^hQoZTCnsjCl|W_@5Fx2(+j0ogy&Y+;L- z<)G$*CiN7hOm^s!{U>1F7U=iNk{+u~dAC!eDz%=|glFW0jEZU1&o(G_c#wTxUjnG} z#cg3>jEpUi#Mlq@t?Msg_#geK^Lx@DyHWf7=AS5vVyM7YOjvUVCfcpVR<(+5!H?9- zySI6s>o3m&*zr||=wcPGyBkQV`EWJl@bH8qobjOp+sXL*)=&yX)8aAbf~tGv?a2SN zu^Ddo-z?DWk9h9Yz#5p^NU#x~wYSd?H@w@!2Gb4G)6-utEMV~~M85Br5ff(v5O1|T z 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, +} + +/// Tauri command to track analytics events from the frontend +#[tauri::command] +pub async fn track_analytics_event(payload: AnalyticsEventPayload) -> Result<(), String> { + tracing::debug!("Tracking analytics event: {}", payload.event); + + let event = TelemetryEvent { event: payload.event, properties: payload.properties }; + + // Track the event asynchronously (Tauri handles the async context) + telemetry::track(event).await; + + Ok(()) +} + +/// Tauri command to flush pending analytics events +#[tauri::command] +pub async fn flush_analytics() -> Result<(), String> { + if let Some(client) = telemetry::client() { + client.flush().await; + Ok(()) + } else { + Err("Telemetry client not initialized".to_string()) + } +} + +/// Tauri command to request app exit +/// +/// Simply closes the window - analytics flush happens in ExitRequested event handler +#[tauri::command] +pub async fn request_exit(app_handle: tauri::AppHandle) -> Result<(), String> { + tracing::debug!("Exit requested via command"); + + // Close the window (will trigger ExitRequested event → analytics flush) + if let Some(window) = app_handle.get_webview_window("main") { + window.close().map_err(|e| e.to_string())?; + } + + Ok(()) +} diff --git a/crates/borders-desktop/src/main.rs b/crates/borders-desktop/src/main.rs new file mode 100644 index 0000000..688407a --- /dev/null +++ b/crates/borders-desktop/src/main.rs @@ -0,0 +1,81 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use crate::plugin::{TauriPlugin, generate_tauri_context}; +use borders_core::app::App; +use borders_core::time::Time; + +mod analytics; +mod plugin; +mod render_bridge; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + let mut app = App::new(); + + // Initialize time tracking + app.insert_resource(Time::new()); + + TauriPlugin::new(|| tauri::Builder::default().plugin(tauri_plugin_opener::init()).plugin(tauri_plugin_process::init()).invoke_handler(tauri::generate_handler![render_bridge::send_frontend_message, render_bridge::handle_render_input, render_bridge::handle_camera_update, render_bridge::handle_map_query, render_bridge::get_game_state, analytics::track_analytics_event, analytics::flush_analytics, analytics::request_exit,]).build(generate_tauri_context()).expect("error while building tauri application")).build_and_run(app); +} + +fn main() { + // Initialize tracing before Bevy + #[cfg(feature = "tracy")] + { + // Initialize Tracy profiler client + let _client = tracy_client::Client::start(); + + use tracing_subscriber::layer::SubscriberExt; + + tracing::subscriber::set_global_default(tracing_subscriber::registry().with(tracing_tracy::TracyLayer::default())).expect("setup tracy layer"); + } + + #[cfg(not(feature = "tracy"))] + { + use tracing_subscriber::fmt::time::FormatTime; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + #[cfg(debug_assertions)] + let log_filter = "borders_core=debug,borders_protocol=debug,borders_desktop=debug,iron_borders=debug,info"; + + #[cfg(not(debug_assertions))] + let log_filter = "borders_core=warn,borders_protocol=warn,iron_borders=warn,error"; + + struct CustomTimeFormat; + + impl FormatTime for CustomTimeFormat { + fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> std::fmt::Result { + let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap(); + let total_secs = now.as_secs(); + let nanos = now.subsec_nanos(); + + let secs_in_day = total_secs % 86400; + let hours = secs_in_day / 3600; + let minutes = (secs_in_day % 3600) / 60; + let seconds = secs_in_day % 60; + let millis = nanos / 1_000_000; + let micros = (nanos / 1_000) % 1_000; + + write!(w, "{:02}:{:02}:{:02}.{:03}{:03}", hours, minutes, seconds, millis, micros) + } + } + + tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new(log_filter)).with(tracing_subscriber::fmt::layer().with_timer(CustomTimeFormat)).init(); + } + + // Log build information + tracing::info!("Iron Borders v{}", borders_core::build_info::VERSION); + tracing::info!("Git: {} | Built: {}", borders_core::build_info::git_commit_short(), borders_core::build_info::BUILD_TIME); + tracing::info!("Ā© 2025 Ryan Walters. All Rights Reserved."); + + // Initialize telemetry + tokio::runtime::Runtime::new().unwrap().block_on(async { + borders_core::telemetry::init(borders_core::telemetry::TelemetryConfig::default()).await; + borders_core::telemetry::track_session_start().await; + }); + tracing::info!("Telemetry initialized"); + + run(); +} diff --git a/crates/borders-desktop/src/plugin.rs b/crates/borders-desktop/src/plugin.rs new file mode 100644 index 0000000..024ceef --- /dev/null +++ b/crates/borders-desktop/src/plugin.rs @@ -0,0 +1,176 @@ +//! Tauri-Bevy integration plugin +//! +//! This module provides the main integration between Tauri and Bevy, handling +//! the main application loop and event bridging. + +use borders_core::app::{App, Plugin, Update}; +use borders_core::time::Time; +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tauri::{Manager, RunEvent}; + +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; +#[cfg(target_arch = "wasm32")] +use web_time::Instant; + +use crate::render_bridge::{TauriRenderBridgeTransport, cache_leaderboard_snapshot_system}; + +const TARGET_FPS: f64 = 60.0; + +pub fn generate_tauri_context() -> tauri::Context { + tauri::generate_context!() +} + +fn setup_tauri_integration(app: &mut App, tauri_app: &tauri::AppHandle, shared_render_state: Arc>>, shared_leaderboard_state: Arc>>) { + tracing::debug!("Setup tauri integration"); + + // Register state for render bridge commands + tauri_app.manage(Arc::new(Mutex::new(None::))); + tauri_app.manage(Arc::new(Mutex::new(None::))); + + // InputState - shared between Tauri commands and ECS systems + let input_state_shared = Arc::new(Mutex::new(borders_core::ui::input::InputState::new())); + tauri_app.manage(input_state_shared.clone()); + app.insert_non_send_resource(input_state_shared); + + // Register shared state with Tauri (for get_game_state command) + tauri_app.manage(shared_render_state.clone()); + tauri_app.manage(shared_leaderboard_state.clone()); + + // Get the message queue from the transport (already added as plugin) + let transport = app.world().get_resource::>().expect("RenderBridge should be added by plugin"); + let message_queue = transport.transport.inbound_messages(); + tauri_app.manage(message_queue); + + // Store shared states in world + app.insert_non_send_resource(shared_leaderboard_state); +} + +pub struct TauriPlugin { + setup: Box tauri::App + Send + Sync>, +} + +impl TauriPlugin { + pub fn new(setup: F) -> Self + where + F: Fn() -> tauri::App + Send + Sync + 'static, + { + Self { setup: Box::new(setup) } + } +} + +impl TauriPlugin { + pub fn build_and_run(self, mut app: App) -> ! { + let tauri_app = (self.setup)(); + + // Create shared state for game state recovery + let shared_render_state = Arc::new(Mutex::new(None::)); + let shared_leaderboard_state = Arc::new(Mutex::new(None::)); + + // Create transport for Tauri frontend (handles both render and UI communication) + let transport = TauriRenderBridgeTransport::new(tauri_app.handle().clone(), shared_render_state.clone()); + + // Add the render bridge plugin to handle all frontend communication + borders_core::ui::FrontendPlugin::new(transport).build(&mut app); + + // Set up Tauri integration directly (no startup system needed) + setup_tauri_integration(&mut app, tauri_app.handle(), shared_render_state, shared_leaderboard_state); + + // Add the leaderboard caching system + app.add_systems(Update, cache_leaderboard_snapshot_system); + + // Run the app + run_tauri_app(app, tauri_app); + std::process::exit(0) + } +} + +pub fn run_tauri_app(app: App, tauri_app: tauri::App) { + let app_rc = Rc::new(RefCell::new(app)); + let mut tauri_app = tauri_app; + let mut is_initialized = false; + let mut last_frame_time = Instant::now(); + + let target_frame_duration = Duration::from_secs_f64(1.0 / TARGET_FPS); + + loop { + let frame_start = Instant::now(); + + #[allow(deprecated)] + tauri_app.run_iteration(move |_app_handle, event: RunEvent| { + match event { + tauri::RunEvent::Ready => { + // Event acknowledged, actual setup happens below + } + tauri::RunEvent::ExitRequested { .. } => { + // Track session end and flush analytics before exit + if borders_core::telemetry::client().is_some() { + tracing::debug!("ExitRequested: tracking session end and flushing analytics"); + + // Create a minimal runtime for blocking operations + let runtime = tokio::runtime::Builder::new_current_thread().enable_time().enable_io().build().expect("Failed to create tokio runtime for flush"); + + runtime.block_on(async { + // Track session end event + borders_core::telemetry::track_session_end().await; + + // Flush all pending events (the batch-triggered send is now synchronous) + if let Some(client) = borders_core::telemetry::client() { + let timeout = std::time::Duration::from_millis(500); + match tokio::time::timeout(timeout, client.flush()).await { + Ok(_) => { + tracing::debug!("Analytics flushed successfully before exit") + } + Err(_) => { + tracing::warn!("Analytics flush timed out after 500ms") + } + } + } + }); + } + } + _ => (), + } + }); + + if tauri_app.webview_windows().is_empty() { + tauri_app.cleanup_before_exit(); + break; + } + + // Initialize game plugin on first iteration after Tauri is ready + if !is_initialized { + let mut app = app_rc.borrow_mut(); + + // Add core game plugin + borders_core::GamePlugin::new(borders_core::plugin::NetworkMode::Local).build(&mut app); + + app.run_startup(); + app.finish(); + app.cleanup(); + + is_initialized = true; + last_frame_time = Instant::now(); // Reset timer after initialization + tracing::info!("Game initialized"); + } + + // Update time resource with delta from PREVIOUS frame + let mut app = app_rc.borrow_mut(); + let delta = frame_start.duration_since(last_frame_time); + if let Some(mut time) = app.world_mut().get_resource_mut::

+ + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..9be4147 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,48 @@ +{ + "name": "iron-borders", + "private": true, + "type": "module", + "scripts": { + "check": "tsc --noEmit --project tsconfig.browser.json", + "dev": "vike dev", + "dev:browser": "vike dev --mode browser", + "build": "tsc --project tsconfig.browser.json && vike build", + "build:desktop": "tsc --project tsconfig.desktop.json && vike build --mode desktop", + "build:browser": "tsc --project tsconfig.browser.json && vike build --mode browser", + "preview": "vike preview", + "preview:browser": "vike preview --mode browser", + "tauri": "tauri" + }, + "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/oswald": "^5.2.8", + "@radix-ui/react-dialog": "^1.1.15", + "@tauri-apps/api": "^2.8.0", + "@tauri-apps/plugin-opener": "^2.5.0", + "@tauri-apps/plugin-process": "^2.3.0", + "lucide-react": "^0.545.0", + "motion": "^12.23.22", + "overlayscrollbars": "^2.12.0", + "overlayscrollbars-react": "^0.5.6", + "pixi.js": "^8.14.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "vike": "^0.4.242", + "vike-react": "^0.6.9" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.8.4", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", + "@vitejs/plugin-react": "^5.0.4", + "lightningcss": "^1.30.2", + "prettier": "^3.6.2", + "typescript": "~5.9.3", + "vite": "^7.1.9", + "vite-imagetools": "^9.0.0" + }, + "browserslist": [ + "last 2 versions and >0.2% and not dead", + "Firefox ESR" + ] +} diff --git a/frontend/pages/+Head.tsx b/frontend/pages/+Head.tsx new file mode 100644 index 0000000..eb32aa8 --- /dev/null +++ b/frontend/pages/+Head.tsx @@ -0,0 +1,73 @@ +// Import fonts CSS as raw string to inline in HTML +import fontsCss from "./fonts.css?inline"; +import oswaldWoff2 from "@fontsource-variable/oswald/files/oswald-latin-wght-normal.woff2?url"; + +export default function HeadDefault() { + return ( + <> + + + + {/* Preload critical Oswald font for faster title rendering */} + + + {/* Inlined font definitions - processed by Vite at build time */} + + + ); +} diff --git a/frontend/pages/+Wrapper.client.tsx b/frontend/pages/+Wrapper.client.tsx new file mode 100644 index 0000000..534fc44 --- /dev/null +++ b/frontend/pages/+Wrapper.client.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState, type ReactNode } from "react"; +import { GameAPIProvider } from "@/shared/api/GameAPIContext"; +import { AnalyticsProvider } from "@/shared/analytics"; + +export default function Wrapper({ children }: { children: ReactNode }) { + const [gameAPI, setGameAPI] = useState(null); + const [analytics, setAnalytics] = useState(null); + + // Dynamically import the appropriate platform implementation + // Use build-time constant for tree-shaking + useEffect(() => { + if (__DESKTOP__) { + Promise.all([import("@/desktop/api/tauriAPI"), import("@/desktop/analytics/tauriAnalytics")]).then( + ([{ tauriAPI }, { tauriAnalytics }]) => { + setGameAPI(tauriAPI); + setAnalytics(tauriAnalytics); + }, + ); + } else { + Promise.all([import("@/browser/api/wasmBridge"), import("@/browser/analytics")]).then(([{ wasmBridge }, { wasmAnalytics }]) => { + setGameAPI(wasmBridge); + setAnalytics(wasmAnalytics); + }); + } + }, []); + + // Browser-specific setup (must be before early return to satisfy Rules of Hooks) + useEffect(() => { + if (!__DESKTOP__) { + // Disable context menu to prevent interference with right-click controls + const handleContextMenu = (e: MouseEvent) => { + e.preventDefault(); + return false; + }; + document.addEventListener("contextmenu", handleContextMenu); + + // Handle user ID storage from worker + const userIdChannel = new BroadcastChannel("user_id_storage"); + userIdChannel.onmessage = (event) => { + try { + const msg = JSON.parse(event.data as string); + if (msg.action === "save") { + localStorage.setItem("app_session_id", msg.id); + } else if (msg.action === "load") { + const id = localStorage.getItem("app_session_id"); + if (id) { + userIdChannel.postMessage(JSON.stringify({ action: "load_response", id })); + } + } + } catch {} + }; + + return () => { + document.removeEventListener("contextmenu", handleContextMenu); + userIdChannel.close(); + }; + } + }, []); + + // Wait for platform-specific modules to load + if (!gameAPI || !analytics) { + return null; + } + + return ( + + {children} + + ); +} diff --git a/frontend/pages/+Wrapper.tsx b/frontend/pages/+Wrapper.tsx new file mode 100644 index 0000000..6d29e6d --- /dev/null +++ b/frontend/pages/+Wrapper.tsx @@ -0,0 +1,17 @@ +import { type ReactNode } from 'react' +import { GameAPIProvider } from '@/shared/api/GameAPIContext' +import { AnalyticsProvider } from '@/shared/analytics' + +// Fonts are imported in +Head.tsx + +// Server-side wrapper provides stub implementations for SSG/pre-rendering +// The real implementations are provided by +Wrapper.client.tsx on the client +export default function Wrapper({ children }: { children: ReactNode }) { + // During SSR/pre-rendering, provide null implementations + // These will be replaced by real implementations when the client-side wrapper takes over + return ( + + {children} + + ) +} diff --git a/frontend/pages/+config.js b/frontend/pages/+config.js new file mode 100644 index 0000000..82ab31f --- /dev/null +++ b/frontend/pages/+config.js @@ -0,0 +1,14 @@ +import vikeReact from "vike-react/config"; + +export default { + extends: [vikeReact], + + // Enable pre-rendering for static site generation + prerender: true, + + // Global head configuration + title: "Iron Borders", + description: "Strategic Territory Control", + // Disable React StrictMode to avoid double-mounting issues with PixiJS + reactStrictMode: false, +}; diff --git a/frontend/pages/fonts.css b/frontend/pages/fonts.css new file mode 100644 index 0000000..b126228 --- /dev/null +++ b/frontend/pages/fonts.css @@ -0,0 +1,19 @@ +/* oswald-latin-wght-normal */ +@font-face { + font-family: 'Oswald Variable'; + font-style: normal; + font-display: block; + font-weight: 200 700; + src: url(@fontsource-variable/oswald/files/oswald-latin-wght-normal.woff2) format('woff2-variations'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +} + +/* inter-latin-wght-normal */ +@font-face { + font-family: 'Inter Variable'; + font-style: normal; + font-display: swap; + font-weight: 100 900; + src: url(@fontsource-variable/inter/files/inter-latin-wght-normal.woff2) format('woff2-variations'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +} diff --git a/frontend/pages/index/+Page.css b/frontend/pages/index/+Page.css new file mode 100644 index 0000000..948fe5c --- /dev/null +++ b/frontend/pages/index/+Page.css @@ -0,0 +1,543 @@ +:root { + font-family: "Inter Variable", Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #1e293b; + background-color: transparent; /* Transparent to show game canvas */ + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +/** + * Input blocking system for desktop mode: + * + * In desktop mode, the InputForwarder component captures all input events + * (mouse, keyboard, wheel) from the window and forwards them to the Bevy game engine. + * + * To prevent UI elements (menus, overlays, etc.) from accidentally forwarding clicks + * to the game, those elements should be tagged with the 'block-game-input' class. + * + * The InputForwarder checks `event.target.closest('.block-game-input')` and skips + * forwarding if the event originated from within a blocking element. + * + * For overlays that SHOULD allow clicks through (like spawn phase instructions), + * simply don't add the 'block-game-input' class. + */ +.block-game-input { + /* This class serves as a marker - no styles needed */ +} + +/* Make the entire React overlay transparent to pointer events */ +html, +body, +#root { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + pointer-events: none; + position: fixed; + top: 0; + left: 0; + background-color: transparent; /* Transparent to show game canvas */ + z-index: 1; /* Above canvas (z-index: 0) */ +} + +/* In browser mode, enable pointer events on the game canvas */ +#game-canvas { + pointer-events: auto; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + pointer-events: none; /* Let clicks pass through to interaction layer */ + position: relative; + z-index: 1; /* Above interaction layer */ +} + +/* Game canvas - PixiJS renderer layer */ +.game-canvas { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: auto; /* Capture input events */ + z-index: 0; /* Behind all UI elements */ +} + +.game-ui { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; /* Let clicks pass through to interaction layer */ + z-index: 1; /* Above interaction layer and canvas */ +} + +h1 { + text-align: center; + pointer-events: auto; /* Make text selectable if needed */ +} + +p { + pointer-events: auto; /* Make text selectable */ +} + +a { + font-weight: 500; + color: #3b82f6; + text-decoration: inherit; + pointer-events: auto; /* Make links clickable */ +} + +a:hover { + color: #2563eb; +} + +form { + pointer-events: auto; /* Make form interactive */ +} + +input, +button { + border-radius: 8px; + border: 1px solid #e2e8f0; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #1e293b; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + pointer-events: auto; /* Re-enable clicks on interactive elements */ +} + +button { + cursor: pointer; + pointer-events: auto; /* Re-enable clicks on buttons */ +} + +button:hover { + border-color: #3b82f6; +} +button:active { + border-color: #3b82f6; + background-color: #f1f5f9; +} + +input, +button { + outline: none; +} + +/* Leaderboard styles */ +.no-drag { + -webkit-app-region: no-drag; +} + +.leaderboard { + position: fixed; + top: 0; + left: 12px; + z-index: 10; + user-select: none; + pointer-events: auto; + opacity: 0.6; + transition: opacity 400ms ease; + font-size: 13px; /* Base font size for scaling everything */ +} + +.leaderboard:hover { + opacity: 1; +} + +.leaderboard__header { + background: rgba(15, 23, 42, 0.75); + color: white; + padding: 0.5em 0.83em; /* 6px 10px scaled to em */ + border-radius: 0 0 0 0; + font-weight: 500; + font-size: 1.25em; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; +} + +.leaderboard__header--collapsed { + border-radius: 0 0 0.2em 0.2em; +} + +.leaderboard__body { + background: rgba(15, 23, 42, 0.6); + backdrop-filter: blur(2px); + border-radius: 0 0 0.5em 0.5em; /* 6px scaled to em */ +} + +.leaderboard__table-container { + /* Height set dynamically via JavaScript */ + overflow: hidden; +} + +.leaderboard__table { + width: 23.5em; /* Increased to accommodate position column */ + border-collapse: collapse; +} + +.leaderboard__player-highlight { + background: rgba(15, 23, 42, 0.65); + backdrop-filter: blur(2px); + border-radius: 0.5em; +} + +.leaderboard__row { + cursor: pointer; + transition: background-color 0.15s ease; + color: rgba(255, 255, 255, 0.75); + font-size: 1.1em; + + > td { + padding-top: 0.25em; + padding-bottom: 0.25em; + } +} + +.leaderboard__row:hover { + background: rgba(255, 255, 255, 0.06); +} + +.leaderboard__row--player { + color: white; +} + +.leaderboard__row--eliminated { + cursor: default; +} + +.leaderboard__row--eliminated:hover { + background: transparent; +} + +.leaderboard__row--highlighted { + background: rgba(255, 255, 255, 0.12) !important; +} + +.leaderboard__position { + padding-left: 0.67em; + padding-right: 0.5em; + text-align: center; + font-variant-numeric: tabular-nums; + white-space: nowrap; + width: 3em; + min-width: 3em; + font-size: 0.9em; + letter-spacing: -0.02em; + vertical-align: middle; +} + +.leaderboard__name { + padding: 0 0.67em 0 0; /* Reduced left padding since position column has right padding */ + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 55%; /* Reduced to make room for position column */ +} + +.leaderboard__name-content { + display: flex; + align-items: center; + gap: 0.5em; +} + +.leaderboard__color-circle { + width: 0.75em; + height: 0.75em; + border-radius: 50%; + flex-shrink: 0; + box-shadow: + 0 0 0.15em rgba(0, 0, 0, 0.4), + 0 0 0.3em rgba(0, 0, 0, 0.2), + 0 0 0.5em rgba(0, 0, 0, 0.1), + inset 0 0 0.1em rgba(255, 255, 255, 0.2); +} + +.leaderboard__percent { + padding: 0 0.67em; /* 6px 8px scaled to em */ + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; + width: 20%; /* Fixed width for percentage column */ +} + +.leaderboard__troops { + padding-left: 0.45em; + padding-right: 0.86em; + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; + width: 20%; /* Fixed width for troops column */ + min-width: 5em; /* 60px scaled to em (60/12) */ +} + +.leaderboard__placeholder { + padding: 1em; + text-align: center; + color: #94a3b8; + font-style: italic; +} + +/* Responsive font sizing based on window size */ +@media (max-width: 1200px) { + .leaderboard { + font-size: 12px; + } +} + +@media (max-width: 1000px) { + .leaderboard { + font-size: 12px; + } +} + +@media (max-width: 800px) { + .leaderboard { + font-size: 11.5px; + } +} + +@media (max-width: 600px) { + .leaderboard { + font-size: 10px; + } +} + +/* Attacks styles */ +.attacks { + user-select: none; + opacity: 0.6; + transition: opacity 400ms ease; + font-size: 13px; /* Base font size for scaling everything */ + display: flex; + flex-direction: column; + gap: 2px; +} + +.attacks:hover { + opacity: 1; +} + +.attacks__row { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5em 0.83em; + border: none; + background: rgba(15, 23, 42, 0.6); + backdrop-filter: blur(2px); + color: white; + cursor: pointer; + transition: background-color 0.15s ease; + font-size: 1.1em; + font-family: inherit; + width: 21.67em; + border-radius: 0.5em; + overflow: hidden; +} + +.attacks__row:hover { + background: rgba(15, 23, 42, 0.75); +} + +.attacks__background { + position: absolute; + top: 0; + right: 0; + height: 100%; + pointer-events: none; + transition: width 0.3s ease; +} + +.attacks__nation { + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + padding-right: 1em; + position: relative; + z-index: 1; +} + +.attacks__troops { + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; + min-width: 5em; + position: relative; + z-index: 1; +} + +@media (max-width: 1200px) { + .attacks { + font-size: 12px; + } +} + +@media (max-width: 1000px) { + .attacks { + font-size: 12px; + } +} + +@media (max-width: 800px) { + .attacks { + font-size: 11.5px; + } +} + +@media (max-width: 600px) { + .attacks { + font-size: 10px; + } +} + +/* Attack Controls styles */ +.attack-controls { + position: fixed; + bottom: 12px; + left: 12px; + z-index: 10; + user-select: none; + opacity: 0.95; + transition: opacity 400ms ease; + font-size: 15.6px; + pointer-events: none; +} + +.attack-controls:hover { + opacity: 1; +} + +.attack-controls__content { + display: flex; + flex-direction: column; + pointer-events: auto; +} + +.attack-controls__header { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 0.5em; + min-width: 26em; +} + +.attack-controls__label { + font-size: 0.7em; + color: white; + opacity: 0.6; + padding-bottom: 0.15em; + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 500; + white-space: nowrap; + flex-shrink: 0; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 0.8), + 0 2px 4px rgba(0, 0, 0, 0.6), + 1px 0 2px rgba(0, 0, 0, 0.8), + -1px 0 2px rgba(0, 0, 0, 0.8); +} + +.attack-controls__attacks { + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; + margin-bottom: 0.25em; +} + +.attack-controls__slider-wrapper { + width: 26em; +} + +.attack-controls__slider { + height: 2.75em; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(2px); + border-radius: 0.4em; + position: relative; + cursor: pointer; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.attack-controls__slider-fill { + height: 100%; + background: rgba(59, 130, 246, 0.6); + position: absolute; + left: 0; + top: 0; + pointer-events: none; + border-radius: 0.15em; +} + +.attack-controls__percentage { + position: absolute; + left: 0.8em; + top: 50%; + transform: translateY(-50%); + font-size: 1.15em; + font-weight: 500; + font-variant-numeric: tabular-nums; + color: white; + pointer-events: none; + z-index: 1; + opacity: 0.85; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 0.4), + 0 2px 4px rgba(0, 0, 0, 0.4), + 1px 0 2px rgba(0, 0, 0, 0.4), + -1px 0 2px rgba(0, 0, 0, 0.4); +} + +@media (max-width: 1200px) { + .attack-controls { + font-size: 14.4px; + } +} + +@media (max-width: 1000px) { + .attack-controls { + font-size: 14.4px; + } +} + +@media (max-width: 800px) { + .attack-controls { + font-size: 13.8px; + } +} + +@media (max-width: 600px) { + .attack-controls { + font-size: 12px; + } +} diff --git a/frontend/pages/index/+Page.tsx b/frontend/pages/index/+Page.tsx new file mode 100644 index 0000000..adcdf75 --- /dev/null +++ b/frontend/pages/index/+Page.tsx @@ -0,0 +1,71 @@ +import { useState, useEffect, lazy, Suspense } from "react"; +import "./+Page.css"; +import { MenuScreen } from "@/shared/components/MenuScreen"; +import { useAnalytics } from "@/shared/analytics"; + +// Lazy load components that aren't needed immediately +const GameContainer = lazy(() => import("./GameContainer.client").then(m => ({ default: m.GameContainer }))); +const AlphaWarningModal = lazy(() => import("@/shared/components/AlphaWarningModal")); + +function App() { + const [showMenu, setShowMenu] = useState(true); + const [isHydrated, setIsHydrated] = useState(false); + const analytics = useAnalytics(); + + // Mark as hydrated after first render to prevent hydration mismatch + useEffect(() => { + setIsHydrated(true); + }, []); + + // Prefetch components after initial render + useEffect(() => { + const timer = setTimeout(() => { + // Trigger chunk downloads without rendering + import("./GameContainer.client"); + import("@/shared/components/AlphaWarningModal"); + }, 2000); + return () => clearTimeout(timer); + }, []); + + // Track app started on mount + useEffect(() => { + if (!analytics) return; + analytics.track("app_started", { + platform: __DESKTOP__ ? "desktop" : "browser", + }); + }, [analytics]); + + const handleStartSingleplayer = () => { + setShowMenu(false); + }; + + const handleExit = async () => { + if (__DESKTOP__) { + const { invoke } = await import("@tauri-apps/api/core"); + await invoke("request_exit").catch((err) => { + console.error("Failed to request exit:", err); + }); + } + }; + + // Don't render game components until hydration is complete + if (!isHydrated) { + return null; + } + + return ( + <> + {/* Menu Screen - pre-renderable, covers everything when visible */} + {showMenu && } + + {/* Game Container - client-only, lazy loaded when game starts */} + {!showMenu && ( + + setShowMenu(true)} /> + + )} + + ); +} + +export default App; diff --git a/frontend/pages/index/GameContainer.client.tsx b/frontend/pages/index/GameContainer.client.tsx new file mode 100644 index 0000000..6993749 --- /dev/null +++ b/frontend/pages/index/GameContainer.client.tsx @@ -0,0 +1,170 @@ +import { useState, useEffect, lazy, Suspense } from "react"; +import { Attacks } from "@/shared/components/Attacks"; +import { AttackControls } from "@/shared/components/AttackControls"; +import { Leaderboard } from "@/shared/components/Leaderboard"; +import { GameMenu } from "@/shared/components/GameMenu"; +import { SpawnPhaseOverlay } from "@/shared/components/SpawnPhaseOverlay"; +import { GameCanvas } from "@/shared/components/GameCanvas"; +import { useGameAPI } from "@/shared/api/GameAPIContext"; +import { useAnalytics } from "@/shared/analytics"; +import type { GameOutcome, LeaderboardSnapshot } from "@/shared/api/types"; +import type { GameRenderer } from "@/shared/render/GameRenderer"; + +const GameEndOverlay = lazy(() => import("@/shared/components/GameEndOverlay")); + +interface GameContainerProps { + onReturnToMenu: () => void; +} + +export function GameContainer({ onReturnToMenu }: GameContainerProps) { + const [gameOutcome, setGameOutcome] = useState(null); + const [spawnPhaseActive, setSpawnPhaseActive] = useState(false); + const [spawnCountdown, setSpawnCountdown] = useState<{ + startedAtMs: number; + durationSecs: number; + } | null>(null); + const [initialGameState, setInitialGameState] = useState(null); + const [initialLeaderboard, setInitialLeaderboard] = useState(null); + const [renderer, setRenderer] = useState(null); + const [highlightedNation, setHighlightedNation] = useState(null); + const api = useGameAPI(); + const analytics = useAnalytics(); + + // Check for existing game state on mount (for reload recovery) + // Skip this in browser since WASM doesn't persist state + useEffect(() => { + if (!api || typeof api.getGameState !== "function") return; + + // Only check for state recovery on desktop (Tauri) + // In browser, this always returns null so skip the async call + if (import.meta.env.MODE === 'browser') return; + + api.getGameState().then((state) => { + // Only recover if we actually have render data (indicates a running game) + if (state && state.render_init) { + console.log("Recovered game state after reload:", state); + setInitialGameState(state.render_init); + setInitialLeaderboard(state.leaderboard_snapshot); + } + }); + }, [api]); + + // Start the game on mount + useEffect(() => { + if (api) { + api.startGame(); + } + // Track game started + if (analytics) { + analytics.track("game_started", { + mode: "singleplayer", + }); + } + }, [api, analytics]); + + // Subscribe to spawn phase events + useEffect(() => { + if (!api) return; + + const unsubUpdate = api.onSpawnPhaseUpdate((update) => { + setSpawnPhaseActive(true); + setSpawnCountdown(update.countdown); + }); + + const unsubEnd = api.onSpawnPhaseEnded(() => { + setSpawnPhaseActive(false); + setSpawnCountdown(null); + }); + + return () => { + unsubUpdate(); + unsubEnd(); + }; + }, [api]); + + // Subscribe to game end events + useEffect(() => { + if (!api) return; + + const unsubscribe = api.onGameEnded((outcome) => { + console.log("Game outcome received:", outcome); + setGameOutcome(outcome); + setSpawnPhaseActive(false); + // Track game ended + if (analytics) { + analytics.track("game_ended", { + outcome: outcome.toString().toLowerCase(), + }); + } + }); + + return () => unsubscribe(); + }, [api, analytics]); + + // Track renderer initialization with GPU info + useEffect(() => { + if (renderer && analytics) { + const rendererInfo = renderer.getRendererInfo(); + analytics.track("renderer_initialized", rendererInfo); + } + }, [renderer, analytics]); + + // Sync highlighted nation with renderer + useEffect(() => { + if (renderer) { + renderer.setHighlightedNation(highlightedNation); + } + }, [highlightedNation, renderer]); + + const handleExit = () => { + if (api) { + api.quitGame(); + } + setGameOutcome(null); + onReturnToMenu(); + }; + + return ( + <> + {/* Game Canvas - always rendered at root */} + + + {/* Game UI */} +
+ {/* Spawn Phase Overlay */} + + + + + + + { + // TODO: Implement settings + }} + /> + + {/* Game End Overlay */} + {gameOutcome && ( + + setGameOutcome(null)} + onExit={handleExit} + /> + + )} +
+ + ); +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..43a2a49 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,2487 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fontsource-variable/inter': + specifier: ^5.2.8 + version: 5.2.8 + '@fontsource-variable/oswald': + specifier: ^5.2.8 + version: 5.2.8 + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tauri-apps/api': + specifier: ^2.8.0 + version: 2.8.0 + '@tauri-apps/plugin-opener': + specifier: ^2.5.0 + version: 2.5.0 + '@tauri-apps/plugin-process': + specifier: ^2.3.0 + version: 2.3.0 + lucide-react: + specifier: ^0.545.0 + version: 0.545.0(react@19.2.0) + motion: + specifier: ^12.23.22 + version: 12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + overlayscrollbars: + specifier: ^2.12.0 + version: 2.12.0 + overlayscrollbars-react: + specifier: ^0.5.6 + version: 0.5.6(overlayscrollbars@2.12.0)(react@19.2.0) + pixi.js: + specifier: ^8.14.0 + version: 8.14.0 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + vike: + specifier: ^0.4.242 + version: 0.4.242(react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(lightningcss@1.30.2)) + vike-react: + specifier: ^0.6.9 + version: 0.6.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vike@0.4.242(react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(lightningcss@1.30.2))) + devDependencies: + '@tauri-apps/cli': + specifier: ^2.8.4 + version: 2.8.4 + '@types/react': + specifier: ^19.2.2 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.2.1 + version: 19.2.1(@types/react@19.2.2) + '@vitejs/plugin-react': + specifier: ^5.0.4 + version: 5.0.4(vite@7.1.9(lightningcss@1.30.2)) + lightningcss: + specifier: ^1.30.2 + version: 1.30.2 + prettier: + specifier: ^3.6.2 + version: 3.6.2 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: ^7.1.9 + version: 7.1.9(lightningcss@1.30.2) + vite-imagetools: + specifier: ^9.0.0 + version: 9.0.0(rollup@4.52.4) + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@brillout/import@0.2.6': + resolution: {integrity: sha512-1GUTmADc8trUC1YSW2lp9r6PmwluMoEyHajnE1kxVdbKGD0wJOlq/DvTWMUqLtBDCnQR+n//qgMtz6HwA/lotA==} + + '@brillout/json-serializer@0.5.21': + resolution: {integrity: sha512-pzzT4U4A9rk7eZpFjloRoMrGG2jnptwNGAhPIH7ZVjCMHd6TaJ29hrERPaY6Bp3Xdzu8JWlHI1o3x7PysxkaHQ==} + + '@brillout/picocolors@1.0.28': + resolution: {integrity: sha512-FBsirUdUlmjmeUlEuqfnUoKAZUt+d0VfMVBYUsFQrDFEV9Rm/QDyG69PSQtQbI6SZQNiZuAWuyhE5USflzx6iQ==} + + '@brillout/require-shim@0.1.2': + resolution: {integrity: sha512-3I4LRHnVZXoSAsEoni5mosq9l6eiJED58d9V954W4CIZ88AUfYBanWGBGbJG3NztaRTpFHEA6wB3Hn93BmmJdg==} + + '@brillout/vite-plugin-server-entry@0.7.15': + resolution: {integrity: sha512-0ClgcmjkhJoHbI6KhbjZlXMeA9qn/EPLXEVssjCE6IVYnVb4bTYuq635c44n7jV3GkjcgFHCQWBmNw0OAGiUvQ==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fontsource-variable/inter@5.2.8': + resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} + + '@fontsource-variable/oswald@5.2.8': + resolution: {integrity: sha512-sIXwfKbyHOATgBWOlpR73VMQ8pA8XCToiyy6sL02ZfYEZPpW18TLtpxPBpYXId7xKbWCousOTzlJsa/9rTd8WQ==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.4': + resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.4': + resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.3': + resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.3': + resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.3': + resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.3': + resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.3': + resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.3': + resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.3': + resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': + resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.3': + resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.4': + resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.4': + resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.4': + resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.4': + resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.4': + resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.4': + resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.4': + resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.4': + resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.4': + resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.4': + resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.4': + resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@pixi/colord@2.9.6': + resolution: {integrity: sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.38': + resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.52.4': + resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.4': + resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.4': + resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.4': + resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.4': + resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.4': + resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.52.4': + resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.52.4': + resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.52.4': + resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.4': + resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.52.4': + resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.52.4': + resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.4': + resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.52.4': + resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.52.4': + resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.52.4': + resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.4': + resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.4': + resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.4': + resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.4': + resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.4': + resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + cpu: [x64] + os: [win32] + + '@tauri-apps/api@2.8.0': + resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==} + + '@tauri-apps/cli-darwin-arm64@2.8.4': + resolution: {integrity: sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.8.4': + resolution: {integrity: sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4': + resolution: {integrity: sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.8.4': + resolution: {integrity: sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@2.8.4': + resolution: {integrity: sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-riscv64-gnu@2.8.4': + resolution: {integrity: sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@2.8.4': + resolution: {integrity: sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@2.8.4': + resolution: {integrity: sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@2.8.4': + resolution: {integrity: sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.8.4': + resolution: {integrity: sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.8.4': + resolution: {integrity: sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.8.4': + resolution: {integrity: sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g==} + engines: {node: '>= 10'} + hasBin: true + + '@tauri-apps/plugin-opener@2.5.0': + resolution: {integrity: sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA==} + + '@tauri-apps/plugin-process@2.3.0': + resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/css-font-loading-module@0.0.12': + resolution: {integrity: sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==} + + '@types/earcut@3.0.0': + resolution: {integrity: sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/react-dom@19.2.1': + resolution: {integrity: sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + + '@vitejs/plugin-react@5.0.4': + resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@webgpu/types@0.1.65': + resolution: {integrity: sha512-cYrHab4d6wuVvDW5tdsfI6/o6vcLMDe6w2Citd1oS51Xxu2ycLCnVo4fqwujfKWijrZMInTJIKcXxteoy21nVA==} + + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + baseline-browser-mapping@2.8.15: + resolution: {integrity: sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==} + hasBin: true + + browserslist@4.26.3: + resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001749: + resolution: {integrity: sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + + electron-to-chromium@1.5.233: + resolution: {integrity: sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + framer-motion@12.23.22: + resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + gifuct-js@2.1.2: + resolution: {integrity: sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==} + + imagetools-core@9.0.0: + resolution: {integrity: sha512-LAU2iVl6MuLbARLrZFEOrgqUFGmHij0FqqOR1/mMndUzJoPz2BU4gCXUhjikgwwmfhBPa/1szwiliUy//ZWafw==} + engines: {node: '>=20.0.0'} + + isbot-fast@1.2.0: + resolution: {integrity: sha512-twjuQzy2gKMDVfKGQyQqrx6Uy4opu/fiVUTTpdqtFsd7OQijIp5oXvb27n5EemYXaijh5fomndJt/SPRLsEdSg==} + engines: {node: '>=6.0.0'} + + ismobilejs@1.1.1: + resolution: {integrity: sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==} + + js-binary-schema-parser@2.0.3: + resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.545.0: + resolution: {integrity: sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + motion-dom@12.23.21: + resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + motion@12.23.22: + resolution: {integrity: sha512-iSq6X9vLHbeYwmHvhK//+U74ROaPnZmBuy60XZzqNl0QtZkWfoZyMDHYnpKuWFv0sNMqHgED8aCXk94LCoQPGg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.23: + resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + + overlayscrollbars-react@0.5.6: + resolution: {integrity: sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==} + peerDependencies: + overlayscrollbars: ^2.0.0 + react: '>=16.8.0' + + overlayscrollbars@2.12.0: + resolution: {integrity: sha512-mWJ5MOkcZ/ljHwfLw8+bN0V9ziGCoNoqULcp994j5DTGNQvnkWKWkA7rnO29Kyew5AoHxUnJ4Ndqfcl0HSQjXg==} + + parse-svg-path@0.1.2: + resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pixi.js@8.14.0: + resolution: {integrity: sha512-ituDiEBb1Oqx56RYwTtC6MjPUhPfF/i15fpUv5oEqmzC/ce3SaSumulJcOjKG7+y0J0Ekl9Rl4XTxaUw+MVFZw==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-streaming@0.4.10: + resolution: {integrity: sha512-w0lkeuXxBEZnGbNyg48TUHXr00TsL/hi+V9D0grQHeNV1qSZKXWnGtMwMDLx44Kvbd4mdQpKZKFRyf9VnGmwcg==} + peerDependencies: + react: '>=19' + react-dom: '>=19' + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + + rollup@4.52.4: + resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.4: + resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + tiny-lru@11.4.5: + resolution: {integrity: sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw==} + engines: {node: '>=12'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + vike-react@0.6.9: + resolution: {integrity: sha512-e5rJzRtH8ne4F7EFQ1cUvYlLxiW3gb9WLEycnpa0XYbRkPZaN9sufLAheDZs0Ql9MQiogHgnGJbk3fxGcsKmpQ==} + peerDependencies: + react: '>=19' + react-dom: '>=19' + vike: '>=0.4.182' + + vike@0.4.242: + resolution: {integrity: sha512-oDogaWBjX26BiI24JXdrFsxFZPzuu1xNDMiwq/wu+BsXd1yDQGbmYQgKfE5u4RHqLPj7OCt1F6Rdg8Yz9s1rdQ==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + react-streaming: '>=0.3.42' + vite: '>=6.3.0' + peerDependenciesMeta: + react-streaming: + optional: true + vite: + optional: true + + vite-imagetools@9.0.0: + resolution: {integrity: sha512-nxTMh7+PkZlOinV1DwgcDAill4+khOF29yQVqYEzleyKEb6qunRDYBYaT9TmGfx4l1fk2xSvDNgbiTYyiekHAw==} + engines: {node: '>=20.0.0'} + + vite@7.1.9: + resolution: {integrity: sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.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 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.4': {} + + '@babel/core@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.26.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@babel/traverse@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@brillout/import@0.2.6': {} + + '@brillout/json-serializer@0.5.21': {} + + '@brillout/picocolors@1.0.28': {} + + '@brillout/require-shim@0.1.2': {} + + '@brillout/vite-plugin-server-entry@0.7.15': + dependencies: + '@brillout/import': 0.2.6 + '@brillout/picocolors': 1.0.28 + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@fontsource-variable/inter@5.2.8': {} + + '@fontsource-variable/oswald@5.2.8': {} + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.3 + optional: true + + '@img/sharp-darwin-x64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.3 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.3': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.3': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.3': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.3': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.3': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.3': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.3': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.3': + optional: true + + '@img/sharp-linux-arm64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.3 + optional: true + + '@img/sharp-linux-arm@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.3 + optional: true + + '@img/sharp-linux-ppc64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.3 + optional: true + + '@img/sharp-linux-s390x@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.3 + optional: true + + '@img/sharp-linux-x64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.3 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 + optional: true + + '@img/sharp-wasm32@0.34.4': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + + '@img/sharp-win32-arm64@0.34.4': + optional: true + + '@img/sharp-win32-ia32@0.34.4': + optional: true + + '@img/sharp-win32-x64@0.34.4': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@pixi/colord@2.9.6': {} + + '@polka/url@1.0.0-next.29': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.1(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.1(@types/react@19.2.2) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@rolldown/pluginutils@1.0.0-beta.38': {} + + '@rollup/pluginutils@5.3.0(rollup@4.52.4)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.4 + + '@rollup/rollup-android-arm-eabi@4.52.4': + optional: true + + '@rollup/rollup-android-arm64@4.52.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.4': + optional: true + + '@rollup/rollup-darwin-x64@4.52.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.4': + optional: true + + '@tauri-apps/api@2.8.0': {} + + '@tauri-apps/cli-darwin-arm64@2.8.4': + optional: true + + '@tauri-apps/cli-darwin-x64@2.8.4': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.8.4': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.8.4': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.8.4': + optional: true + + '@tauri-apps/cli-linux-riscv64-gnu@2.8.4': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.8.4': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.8.4': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.8.4': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.8.4': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.8.4': + optional: true + + '@tauri-apps/cli@2.8.4': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.8.4 + '@tauri-apps/cli-darwin-x64': 2.8.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.8.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.8.4 + '@tauri-apps/cli-linux-arm64-musl': 2.8.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.8.4 + '@tauri-apps/cli-linux-x64-gnu': 2.8.4 + '@tauri-apps/cli-linux-x64-musl': 2.8.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.8.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.8.4 + '@tauri-apps/cli-win32-x64-msvc': 2.8.4 + + '@tauri-apps/plugin-opener@2.5.0': + dependencies: + '@tauri-apps/api': 2.8.0 + + '@tauri-apps/plugin-process@2.3.0': + dependencies: + '@tauri-apps/api': 2.8.0 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/css-font-loading-module@0.0.12': {} + + '@types/earcut@3.0.0': {} + + '@types/estree@1.0.8': {} + + '@types/react-dom@19.2.1(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react@19.2.2': + dependencies: + csstype: 3.1.3 + + '@vitejs/plugin-react@5.0.4(vite@7.1.9(lightningcss@1.30.2))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.38 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.1.9(lightningcss@1.30.2) + transitivePeerDependencies: + - supports-color + + '@webgpu/types@0.1.65': {} + + '@xmldom/xmldom@0.8.11': {} + + acorn@8.15.0: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + baseline-browser-mapping@2.8.15: {} + + browserslist@4.26.3: + dependencies: + baseline-browser-mapping: 2.8.15 + caniuse-lite: 1.0.30001749 + electron-to-chromium: 1.5.233 + node-releases: 2.0.23 + update-browserslist-db: 1.1.3(browserslist@4.26.3) + + buffer-from@1.1.2: {} + + cac@6.7.14: {} + + caniuse-lite@1.0.30001749: {} + + convert-source-map@2.0.0: {} + + csstype@3.1.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + earcut@3.0.2: {} + + electron-to-chromium@1.5.233: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escalade@3.2.0: {} + + estree-walker@2.0.2: {} + + eventemitter3@5.0.1: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + motion-dom: 12.23.21 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + gifuct-js@2.1.2: + dependencies: + js-binary-schema-parser: 2.0.3 + + imagetools-core@9.0.0: {} + + isbot-fast@1.2.0: {} + + ismobilejs@1.1.1: {} + + js-binary-schema-parser@2.0.3: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.545.0(react@19.2.0): + dependencies: + react: 19.2.0 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + motion-dom@12.23.21: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + framer-motion: 12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.23: {} + + overlayscrollbars-react@0.5.6(overlayscrollbars@2.12.0)(react@19.2.0): + dependencies: + overlayscrollbars: 2.12.0 + react: 19.2.0 + + overlayscrollbars@2.12.0: {} + + parse-svg-path@0.1.2: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pixi.js@8.14.0: + dependencies: + '@pixi/colord': 2.9.6 + '@types/css-font-loading-module': 0.0.12 + '@types/earcut': 3.0.0 + '@webgpu/types': 0.1.65 + '@xmldom/xmldom': 0.8.11 + earcut: 3.0.2 + eventemitter3: 5.0.1 + gifuct-js: 2.1.2 + ismobilejs: 1.1.1 + parse-svg-path: 0.1.2 + tiny-lru: 11.4.5 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.6.2: {} + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + + react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@brillout/import': 0.2.6 + '@brillout/json-serializer': 0.5.21 + '@brillout/picocolors': 1.0.28 + isbot-fast: 1.2.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + react@19.2.0: {} + + rollup@4.52.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.4 + '@rollup/rollup-android-arm64': 4.52.4 + '@rollup/rollup-darwin-arm64': 4.52.4 + '@rollup/rollup-darwin-x64': 4.52.4 + '@rollup/rollup-freebsd-arm64': 4.52.4 + '@rollup/rollup-freebsd-x64': 4.52.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 + '@rollup/rollup-linux-arm-musleabihf': 4.52.4 + '@rollup/rollup-linux-arm64-gnu': 4.52.4 + '@rollup/rollup-linux-arm64-musl': 4.52.4 + '@rollup/rollup-linux-loong64-gnu': 4.52.4 + '@rollup/rollup-linux-ppc64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-musl': 4.52.4 + '@rollup/rollup-linux-s390x-gnu': 4.52.4 + '@rollup/rollup-linux-x64-gnu': 4.52.4 + '@rollup/rollup-linux-x64-musl': 4.52.4 + '@rollup/rollup-openharmony-arm64': 4.52.4 + '@rollup/rollup-win32-arm64-msvc': 4.52.4 + '@rollup/rollup-win32-ia32-msvc': 4.52.4 + '@rollup/rollup-win32-x64-gnu': 4.52.4 + '@rollup/rollup-win32-x64-msvc': 4.52.4 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + sharp@0.34.4: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.4 + '@img/sharp-darwin-x64': 0.34.4 + '@img/sharp-libvips-darwin-arm64': 1.2.3 + '@img/sharp-libvips-darwin-x64': 1.2.3 + '@img/sharp-libvips-linux-arm': 1.2.3 + '@img/sharp-libvips-linux-arm64': 1.2.3 + '@img/sharp-libvips-linux-ppc64': 1.2.3 + '@img/sharp-libvips-linux-s390x': 1.2.3 + '@img/sharp-libvips-linux-x64': 1.2.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 + '@img/sharp-linux-arm': 0.34.4 + '@img/sharp-linux-arm64': 0.34.4 + '@img/sharp-linux-ppc64': 0.34.4 + '@img/sharp-linux-s390x': 0.34.4 + '@img/sharp-linux-x64': 0.34.4 + '@img/sharp-linuxmusl-arm64': 0.34.4 + '@img/sharp-linuxmusl-x64': 0.34.4 + '@img/sharp-wasm32': 0.34.4 + '@img/sharp-win32-arm64': 0.34.4 + '@img/sharp-win32-ia32': 0.34.4 + '@img/sharp-win32-x64': 0.34.4 + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + tiny-lru@11.4.5: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + totalist@3.0.1: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + update-browserslist-db@1.1.3(browserslist@4.26.3): + dependencies: + browserslist: 4.26.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + vike-react@0.6.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vike@0.4.242(react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(lightningcss@1.30.2))): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-streaming: 0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + vike: 0.4.242(react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(lightningcss@1.30.2)) + + vike@0.4.242(react-streaming@0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(lightningcss@1.30.2)): + dependencies: + '@brillout/import': 0.2.6 + '@brillout/json-serializer': 0.5.21 + '@brillout/picocolors': 1.0.28 + '@brillout/require-shim': 0.1.2 + '@brillout/vite-plugin-server-entry': 0.7.15 + acorn: 8.15.0 + cac: 6.7.14 + es-module-lexer: 1.7.0 + esbuild: 0.25.10 + json5: 2.2.3 + magic-string: 0.30.19 + picomatch: 4.0.3 + semver: 7.7.3 + sirv: 3.0.2 + source-map-support: 0.5.21 + tinyglobby: 0.2.15 + optionalDependencies: + react-streaming: 0.4.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + vite: 7.1.9(lightningcss@1.30.2) + + vite-imagetools@9.0.0(rollup@4.52.4): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.52.4) + imagetools-core: 9.0.0 + sharp: 0.34.4 + transitivePeerDependencies: + - rollup + + vite@7.1.9(lightningcss@1.30.2): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.4 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + lightningcss: 1.30.2 + + yallist@3.1.1: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml new file mode 100644 index 0000000..d0b7dbe --- /dev/null +++ b/frontend/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - esbuild + - sharp diff --git a/frontend/src/assets/multiplayer.png b/frontend/src/assets/multiplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..d62be25313e66504a271d2e48479b9ff7275b8a2 GIT binary patch literal 3117 zcmXYzdpy(oAIHCIJ4PpSJL|gft$ejQH z$lKf5xB>u#2|mA(l>x6isZF8aO^#~kc?AHVquT>=**3rk0Dhdbw>f?)g1PYCJY)Q| zdfXJ5^A`8Su&~7Kn8aQPBxRJ?J*N{7>zusIg6`|cr=`EV1UDQ}eMMSkZLXPh>`3|p zSsw^{F8%58V*~b>KG1QSJu>GlyWi}r?{As0z2-l2AcCS$n_U0K6{*bin^#W)O^X9L zh8sqFS^bSz4#e+D(38yx#Y2^lFP6bC>qnmD_@d&lRVlrE@odAa$(tNGcwFx)!MbP) z_|iY|o*aNi0i;*46#<W9lK$ zeq0c*t?FX+nXvY**=zF_kATyrFHd1ed;q_LpGRQjl#g!b)4a=$3K)9q&hp9a6Urj+ zFl(0#nNnUiNuZ9vYn$^lWu6TInu-h_m`#etA!$Apa93sofEiJc?G1Gvxd-lJeE-qV zUB@J#B5hZ;5@Wik#^Bx(HgxO(ov(85n{XoYyuCb5|9*GRZ6wvf|4y+0U}<45$Renw z=KHcd6QU=5IS`G4^1L4rfamsX(NxZU9B}^s!=|z6j2;fKxoTkg#ubW?uFmooHh6tb z?d(@w^tM0EAk^1vgW~Zf&slKCU`gT*feT4)ddkFJh}2i$K!}b(UmMC~6j2}Y69fQ^ zPeg2&HXiDd&6@8iADJ~@dF7`U97;#Bixi4vf56Lf)$ZOIC
+ +
+ Alpha Software + + This application is in early development. Expect bugs, missing features, and breaking changes. Your feedback is + appreciated as we continue building. + +
+ + + + ); +} + +export default AlphaWarningModal; diff --git a/frontend/src/shared/components/AttackControls.tsx b/frontend/src/shared/components/AttackControls.tsx new file mode 100644 index 0000000..90f8709 --- /dev/null +++ b/frontend/src/shared/components/AttackControls.tsx @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState, ReactNode } from "react"; +import { useGameAPI } from "@/shared/api/GameAPIContext"; +import { useThrottledCallback } from "@/shared/hooks"; + +interface AttackControlsProps { + children?: ReactNode; +} + +export function AttackControls({ children }: AttackControlsProps) { + const api = useGameAPI(); + const [percentage, setPercentage] = useState(50); + const sliderRef = useRef(null); + const isDraggingRef = useRef(false); + + const updateAttackRatioThrottled = useThrottledCallback((percent: number) => { + if (api && typeof api.sendAttackRatio === "function") { + api.sendAttackRatio(percent / 100); + } + }, 50); + + const handlePercentageChange = (newPercent: number) => { + const clampedPercent = Math.max(1, Math.min(100, Math.round(newPercent))); + setPercentage(clampedPercent); + updateAttackRatioThrottled(clampedPercent); + }; + + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + isDraggingRef.current = true; + updateSliderFromMouse(e); + }; + + const handleTouchStart = (e: React.TouchEvent) => { + e.preventDefault(); + isDraggingRef.current = true; + updateSliderFromTouch(e); + }; + + const updateSliderFromMouse = (e: React.MouseEvent | MouseEvent) => { + if (!sliderRef.current) return; + + const rect = sliderRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const width = rect.width; + const rawPercent = (x / width) * 100; + handlePercentageChange(rawPercent); + }; + + const updateSliderFromTouch = (e: React.TouchEvent | TouchEvent) => { + if (!sliderRef.current) return; + + const touch = e.touches[0]; + if (!touch) return; + + const rect = sliderRef.current.getBoundingClientRect(); + const x = touch.clientX - rect.left; + const width = rect.width; + const rawPercent = (x / width) * 100; + handlePercentageChange(rawPercent); + }; + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (isDraggingRef.current) { + updateSliderFromMouse(e); + } + }; + + const handleMouseUp = () => { + isDraggingRef.current = false; + }; + + const handleTouchMove = (e: TouchEvent) => { + if (isDraggingRef.current) { + e.preventDefault(); + updateSliderFromTouch(e); + } + }; + + const handleTouchEnd = () => { + isDraggingRef.current = false; + }; + + const handleGlobalWheel = (e: WheelEvent) => { + if (!e.shiftKey) return; + + e.preventDefault(); + e.stopPropagation(); + + const delta = e.deltaY > 0 ? -5 : 5; + + let newPercent: number; + if (percentage === 1 && delta > 0) { + newPercent = 5; + } else { + newPercent = percentage + delta; + } + + handlePercentageChange(newPercent); + }; + + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("touchmove", handleTouchMove, { passive: false }); + window.addEventListener("touchend", handleTouchEnd); + window.addEventListener("wheel", handleGlobalWheel, { passive: false }); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", handleTouchEnd); + window.removeEventListener("wheel", handleGlobalWheel); + }; + }, [percentage, handlePercentageChange]); + + const handleWheel = (e: React.WheelEvent) => { + if (!e.shiftKey) return; + + e.preventDefault(); + e.stopPropagation(); + + const delta = e.deltaY > 0 ? -5 : 5; + + let newPercent: number; + if (percentage === 1 && delta > 0) { + newPercent = 5; + } else { + newPercent = percentage + delta; + } + + handlePercentageChange(newPercent); + }; + + return ( +
+
+
+
Troops
+
{children}
+
+
+
+
+
{percentage}%
+
+
+
+
+ ); +} diff --git a/frontend/src/shared/components/AttackRow.tsx b/frontend/src/shared/components/AttackRow.tsx new file mode 100644 index 0000000..1d6f12d --- /dev/null +++ b/frontend/src/shared/components/AttackRow.tsx @@ -0,0 +1,73 @@ +import type { AttackEntry, LeaderboardEntry } from "@/shared/api/types"; + +// Format troop count for human-readable display +// 100 => "100", 12,493 => "12.4k", 980,455 => "980k" +export function formatTroopCount(count: number): string { + if (count < 1000) return count.toString(); + if (count < 1000000) { + const k = count / 1000; + return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`; + } + const m = count / 1000000; + return m % 1 === 0 ? `${m}M` : `${m.toFixed(1)}M`; +} + +// Calculate the background width percentage based on troop count +// Uses power scale (x^0.4): 100 troops = 5%, 1k = 7.9%, 10k = 15.3%, 100k = 33.7%, 1M = 80% +function calculateBackgroundWidth(troops: number): number { + const minTroops = 100; + const maxTroops = 1000000; + const minWidth = 5; + const maxWidth = 80; + + // Clamp troops to range + const clampedTroops = Math.max(minTroops, Math.min(maxTroops, troops)); + + // Power scale with exponent 0.4 provides gentler progression than logarithmic + const powerMin = Math.pow(minTroops, 0.4); + const powerMax = Math.pow(maxTroops, 0.4); + const powerTroops = Math.pow(clampedTroops, 0.4); + + // Map to 5-80% range + const normalized = (powerTroops - powerMin) / (powerMax - powerMin); + return minWidth + normalized * (maxWidth - minWidth); +} + +interface AttackRowProps { + attack: AttackEntry; + playerMap: Map; + onNationHover?: (nationId: number | null) => void; +} + +export function AttackRow({ attack, playerMap, onNationHover }: AttackRowProps) { + // For outgoing attacks, show target's name (who we're attacking) + // For incoming attacks, show attacker's name (who is attacking us) + const displayPlayerId = attack.is_outgoing ? attack.target_id : attack.attacker_id; + + const displayPlayer = displayPlayerId !== null ? playerMap.get(displayPlayerId) : null; + const displayName = displayPlayer?.name || "Unclaimed Territory"; + + const backgroundWidth = calculateBackgroundWidth(attack.troops); + const backgroundColor = attack.is_outgoing + ? "rgba(59, 130, 246, 0.3)" // Blue with 30% opacity + : "rgba(239, 68, 68, 0.3)"; // Red with 30% opacity + + return ( + + ); +} diff --git a/frontend/src/shared/components/Attacks.tsx b/frontend/src/shared/components/Attacks.tsx new file mode 100644 index 0000000..7ed275f --- /dev/null +++ b/frontend/src/shared/components/Attacks.tsx @@ -0,0 +1,74 @@ +import { useEffect, useState } from "react"; +import { useGameAPI } from "@/shared/api/GameAPIContext"; +import type { AttacksUpdatePayload, LeaderboardEntry, UnsubscribeFn } from "@/shared/api/types"; +import { AttackRow } from "@/shared/components/AttackRow"; + +export function Attacks({ + onNationHover +}: { + onNationHover: (nationId: number | null) => void; +}) { + const gameAPI = useGameAPI(); + const [attacksData, setAttacksData] = useState(null); + const [playerMap, setPlayerMap] = useState>(new Map()); + + useEffect(() => { + if (!gameAPI) return; + + let unsubscribeAttacks: UnsubscribeFn = () => {}; + let unsubscribeLeaderboard: UnsubscribeFn = () => {}; + + // Subscribe to leaderboard snapshots to get player names/colors + unsubscribeLeaderboard = gameAPI.onLeaderboardSnapshot((snapshot) => { + const newMap = new Map(); + snapshot.entries.forEach((entry) => { + newMap.set(entry.id, entry); + }); + + // Only update state if the map actually changed (prevents unnecessary re-renders) + setPlayerMap((prevMap) => { + // Check if sizes differ + if (prevMap.size !== newMap.size) { + return newMap; + } + // Check if any entries changed + for (const [id, entry] of newMap) { + const prevEntry = prevMap.get(id); + if (!prevEntry || prevEntry.name !== entry.name || prevEntry.color !== entry.color) { + return newMap; + } + } + // No changes - return previous map to avoid re-render + return prevMap; + }); + }); + + // Subscribe to attacks updates + unsubscribeAttacks = gameAPI.onAttacksUpdate((payload) => { + setAttacksData(payload); + }); + + return () => { + unsubscribeAttacks(); + unsubscribeLeaderboard(); + }; + }, [gameAPI]); + + // Don't render if no attacks + if (!attacksData || attacksData.entries.length === 0) { + return null; + } + + return ( +
onNationHover(null)}> + {attacksData.entries.map((attack, index) => ( + + ))} +
+ ); +} diff --git a/frontend/src/shared/components/GameCanvas.tsx b/frontend/src/shared/components/GameCanvas.tsx new file mode 100644 index 0000000..10f61bd --- /dev/null +++ b/frontend/src/shared/components/GameCanvas.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useRef, useState } from "react"; +import { CameraStateListener, GameRenderer } from "@/shared/render"; +import { useGameAPI } from "@/shared/api"; +import { useThrottledCallback } from "@/shared/hooks"; + +interface GameCanvasProps { + className?: string; + initialState?: any | null; + onRendererReady?: (renderer: GameRenderer | null) => void; + onNationHover?: (nationId: number | null) => void; +} + +export const GameCanvas: React.FC = ({ className, initialState, onRendererReady, onNationHover }) => { + const canvasRef = useRef(null); + const rendererRef = useRef(null); + const [isInitialized, setIsInitialized] = useState(false); + const lastHoveredNationRef = useRef(null); + const api = useGameAPI(); + + // Apply initial state when provided (for reload recovery) + useEffect(() => { + if (!initialState || !rendererRef.current || !isInitialized) return; + + console.log("Applying initial game state to renderer:", initialState); + const renderer = rendererRef.current; + + try { + // Apply RenderInit data: terrain palette, terrain, player palette, territories + renderer.setTerrainPalette(initialState.terrain_palette); + renderer.initTerrain(initialState.terrain); + renderer.initPalette(initialState.palette); + renderer.applyTerritorySnapshot(initialState.initial_territories.turn, initialState.initial_territories.territories); + console.log("Initial state applied successfully"); + } catch (err) { + console.error("Failed to apply initial state:", err); + } + }, [initialState, isInitialized]); + + // Initialize renderer once on mount + useEffect(() => { + if (!canvasRef.current || rendererRef.current) return; + + let cancelled = false; + const renderer = new GameRenderer(); + rendererRef.current = renderer; + + // Initialize renderer + renderer + .init(canvasRef.current) + .then(async () => { + if (cancelled) { + renderer.destroy(); + return; + } + + setIsInitialized(true); + + // Set up camera listener + const cameraListener: CameraStateListener = { + onCameraUpdate: (x, y, zoom) => { + if (api && typeof api.sendCameraUpdate === "function") { + api.sendCameraUpdate({ x, y, zoom }); + } + }, + }; + renderer.setCameraListener(cameraListener); + + // Register renderer with API for receiving updates + if (api && typeof api.setRenderer === "function") { + await api.setRenderer(renderer); + } + + // Notify parent that renderer is ready + onRendererReady?.(renderer); + }) + .catch((err: unknown) => { + if (!cancelled) { + console.error("Failed to initialize GameRenderer:", err); + } + }); + + // Cleanup + return () => { + cancelled = true; + if (rendererRef.current) { + // Unregister renderer from API before destroying + if (api && typeof api.setRenderer === "function") { + api.setRenderer(null); + } + rendererRef.current.destroy(); + rendererRef.current = null; + onRendererReady?.(null); + } + setIsInitialized(false); + }; + }, [api, onRendererReady]); + + // Handle canvas clicks using native event listener (after renderer initialization) + // React onClick doesn't work reliably when native listeners are attached to canvas + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas || !rendererRef.current || !api) return; + + const handleCanvasClick = (e: MouseEvent) => { + if (!rendererRef.current || !api) return; + + // Ignore clicks that were camera drags + if (rendererRef.current.hadCameraInteraction()) { + return; + } + + const rect = canvas.getBoundingClientRect(); + const screenX = e.clientX - rect.left; + const screenY = e.clientY - rect.top; + + const tileIndex = rendererRef.current.screenToTile(screenX, screenY); + + if (typeof api.sendMapClick === "function") { + const worldPos = rendererRef.current.tileToWorld(tileIndex || 0); + api.sendMapClick({ + tile_index: tileIndex, + world_x: worldPos.x, + world_y: worldPos.y, + button: e.button, + }); + } + }; + + canvas.addEventListener("click", handleCanvasClick); + return () => canvas.removeEventListener("click", handleCanvasClick); + }, [api, isInitialized]); + + // Handle mouse move (for hover) - throttled to reduce IPC overhead + const handleMouseMove = useThrottledCallback((e: React.MouseEvent) => { + if (!rendererRef.current) return; + + const rect = canvasRef.current!.getBoundingClientRect(); + const screenX = e.clientX - rect.left; + const screenY = e.clientY - rect.top; + + const tileIndex = rendererRef.current.screenToTile(screenX, screenY); + if (tileIndex === null) return; + + // Handle nation hover detection + if (onNationHover) { + const nationId = rendererRef.current.getNationAtTile(tileIndex); + if (nationId !== lastHoveredNationRef.current) { + lastHoveredNationRef.current = nationId; + onNationHover(nationId); + } + } + + // Send map hover to API + if (api && typeof api.sendMapHover === "function") { + const worldPos = rendererRef.current.tileToWorld(tileIndex); + // Only send if we have valid coordinates + if (isFinite(worldPos.x) && isFinite(worldPos.y)) { + api.sendMapHover({ + tile_index: tileIndex, + world_x: worldPos.x, + world_y: worldPos.y, + }); + } + } + }, 10); + + // Handle keyboard input + useEffect(() => { + if (!api) return; + + const handleKeyDown = (e: KeyboardEvent) => { + if (typeof api.sendKeyPress === "function") { + api.sendKeyPress({ + key: e.code, + pressed: true, + }); + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + if (typeof api.sendKeyPress === "function") { + api.sendKeyPress({ + key: e.code, + pressed: false, + }); + } + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }, [api]); + + return ( +
+ e.preventDefault()} + style={{ + display: "block", + width: "100%", + height: "100%", + cursor: isInitialized ? undefined : "wait", + }} + /> + {!isInitialized && ( +
+ Initializing renderer... +
+ )} +
+ ); +}; diff --git a/frontend/src/shared/components/GameEndOverlay.tsx b/frontend/src/shared/components/GameEndOverlay.tsx new file mode 100644 index 0000000..17d831c --- /dev/null +++ b/frontend/src/shared/components/GameEndOverlay.tsx @@ -0,0 +1,133 @@ +import type { GameOutcome } from "@/shared/api/types"; + +interface GameEndOverlayProps { + outcome: GameOutcome; + onSpectate: () => void; + onExit: () => void; +} + +export function GameEndOverlay({ outcome, onSpectate, onExit }: GameEndOverlayProps) { + return ( +
+
+ {/* Victory/Defeat text */} +
+
+ {outcome === "Victory" ? "Victory" : "Defeat"} +
+ {/* TODO: Make subtitle dynamic based on win/loss condition: + - Victory by elimination: "You destroyed all other Nations." + - Victory by occupation: "You reached 80% occupation of the map." + - Defeat by elimination: "Your nation was eradicated." + - Defeat by enemy occupation: "{PlayerName} reached 80% occupation of the map." + */} +
+ {outcome === "Victory" ? "You conquered the map." : "Your nation fell."} +
+
+ + {/* Button row */} +
+ + +
+
+
+ ); +} + +export default GameEndOverlay; diff --git a/frontend/src/shared/components/GameMenu.css b/frontend/src/shared/components/GameMenu.css new file mode 100644 index 0000000..e910519 --- /dev/null +++ b/frontend/src/shared/components/GameMenu.css @@ -0,0 +1,176 @@ +.game-menu { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 999; +} + +.game-menu .menu-toggle { + background: rgba(15, 23, 42, 0.75); + border: none; + border-radius: 0.375rem; + color: white; + padding: 0.5rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.15s ease; +} + +.game-menu .menu-toggle:hover { + background: rgba(15, 23, 42, 0.9); +} + +.game-menu .menu-dropdown { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + background: rgba(15, 23, 42, 0.75); + border-radius: 0.375rem; + min-width: 140px; + overflow: hidden; +} + +.game-menu .menu-dropdown-button { + width: 100%; + background: transparent; + border: none; + color: white; + padding: 0.75rem 1rem; + text-align: right; + cursor: pointer; + font-size: 0.95rem; + font-family: "Inter Variable", sans-serif; + transition: background-color 0.15s ease; +} + +.game-menu .menu-dropdown-button:hover { + background: rgba(255, 255, 255, 0.06); +} + +.game-menu .menu-dropdown-button.with-border { + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.game-menu .confirmation-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + animation: fadeIn 0.2s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.game-menu .confirmation-overlay.closing { + animation: fadeOut 0.2s ease-out forwards; +} + +.game-menu .confirmation-dialog { + background: rgba(15, 23, 42, 0.95); + border-radius: 0.5rem; + padding: 2rem; + min-width: 300px; + max-width: 400px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); + animation: slideUp 0.2s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideDown { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(20px); + } +} + +.game-menu .confirmation-overlay.closing .confirmation-dialog { + animation: slideDown 0.2s ease-out forwards; +} + +.game-menu .confirmation-title { + margin: 0 0 1rem 0; + color: white; + font-size: 1.25rem; + font-family: "Inter Variable", sans-serif; + font-weight: 600; +} + +.game-menu .confirmation-message { + margin: 0 0 1.5rem 0; + color: rgba(255, 255, 255, 0.8); + font-size: 0.95rem; + font-family: "Inter Variable", sans-serif; + line-height: 1.5; +} + +.game-menu .confirmation-actions { + display: flex; + gap: 0.75rem; + justify-content: flex-end; +} + +.game-menu .confirmation-button { + border: none; + border-radius: 0.375rem; + color: white; + padding: 0.625rem 1.25rem; + cursor: pointer; + font-size: 0.95rem; + font-family: "Inter Variable", sans-serif; + transition: background-color 0.15s ease; +} + +.game-menu .confirmation-button.cancel { + background: rgba(255, 255, 255, 0.1); +} + +.game-menu .confirmation-button.cancel:hover { + background: rgba(255, 255, 255, 0.15); +} + +.game-menu .confirmation-button.confirm { + background: rgba(239, 68, 68, 0.9); + font-weight: 500; +} + +.game-menu .confirmation-button.confirm:hover { + background: rgba(220, 38, 38, 0.9); +} diff --git a/frontend/src/shared/components/GameMenu.tsx b/frontend/src/shared/components/GameMenu.tsx new file mode 100644 index 0000000..9193063 --- /dev/null +++ b/frontend/src/shared/components/GameMenu.tsx @@ -0,0 +1,75 @@ +import { useState } from "react"; +import { Menu, X } from "lucide-react"; +import "./GameMenu.css"; + +interface GameMenuProps { + onExit: () => void; + onSettings?: () => void; +} + +export function GameMenu({ onExit, onSettings }: GameMenuProps) { + const [isOpen, setIsOpen] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + const [isClosing, setIsClosing] = useState(false); + + const closeConfirmation = () => { + setIsClosing(true); + setTimeout(() => { + setShowConfirmation(false); + setIsClosing(false); + }, 200); + }; + + return ( +
+ {/* Hamburger/Close Button */} + + + {/* Dropdown Menu */} + {isOpen && ( +
+ +
+ )} + + {/* Confirmation Dialog */} + {showConfirmation && ( +
+
e.stopPropagation()}> +

Are you sure?

+

You will not be able to return to this game after exiting.

+
+ + +
+
+
+ )} +
+ ); +} diff --git a/frontend/src/shared/components/Leaderboard.tsx b/frontend/src/shared/components/Leaderboard.tsx new file mode 100644 index 0000000..442837f --- /dev/null +++ b/frontend/src/shared/components/Leaderboard.tsx @@ -0,0 +1,226 @@ +import { useEffect, useMemo, useState, useRef } from "react"; +import { ChevronRight, ChevronDown } from "lucide-react"; +import { useGameAPI } from "@/shared/api/GameAPIContext"; +import type { LeaderboardSnapshot, UnsubscribeFn } from "@/shared/api/types"; +import * as motion from "motion/react-client"; +import type { Transition } from "motion/react"; + +// Smart precision algorithm for percentage display +function calculatePrecision(percentages: number[]): number { + if (percentages.length === 0) return 0; + + // Find the minimum non-zero difference between consecutive percentages + const sorted = [...percentages].sort((a, b) => b - a); + let minDiff = Infinity; + + for (let i = 0; i < sorted.length - 1; i++) { + const diff = sorted[i] - sorted[i + 1]; + if (diff > 0) { + minDiff = Math.min(minDiff, diff); + } + } + + // If all percentages are the same, use 0 decimal places + if (minDiff === Infinity) return 0; + + // Determine precision based on the minimum difference + if (minDiff >= 0.1) return 0; // 0.1% or more difference -> 0 decimals + if (minDiff >= 0.01) return 1; // 0.01% or more difference -> 1 decimal + return 2; // 0.001% or more difference -> 2 decimals (max precision) +} + +const VISIBLE_TOP_N = 8; +const RENDERED_BUFFER = 15; + +const spring: Transition = { + type: "spring", + damping: 20, + stiffness: 300, +}; + +export function Leaderboard({ + initialSnapshot, + highlightedNation, + onNationHover, +}: { + initialSnapshot?: LeaderboardSnapshot | null; + highlightedNation: number | null; + onNationHover: (nationId: number | null) => void; +}) { + const gameAPI = useGameAPI(); + const [collapsed, setCollapsed] = useState(false); + const [snapshot, setSnapshot] = useState(initialSnapshot || null); + const [status, setStatus] = useState<"loading" | "waiting" | "ready" | "error">(initialSnapshot ? "ready" : "waiting"); + const [containerHeight, setContainerHeight] = useState(null); + const tableRef = useRef(null); + + useEffect(() => { + if (!gameAPI) return; + + let unsubscribe: UnsubscribeFn = () => {}; + + // Subscribe to leaderboard snapshots + try { + unsubscribe = gameAPI.onLeaderboardSnapshot((snapshotData) => { + setSnapshot(snapshotData); + setStatus("ready"); + }); + } catch (error) { + console.warn("Failed to subscribe to leaderboard snapshots:", error); + setStatus("error"); + } + + return () => { + unsubscribe(); + }; + }, [gameAPI]); + + const { topRows, playerEntry, playerInTopN, playerPosition } = useMemo(() => { + if (!snapshot) { + return { + topRows: [], + playerEntry: null, + playerInTopN: false, + playerPosition: null, + }; + } + + // Render top 15 rows for animation buffer (only top 8 will be visible) + const topRows = snapshot.entries.slice(0, RENDERED_BUFFER); + + // Find player and check if they're in visible top N + const playerEntry = snapshot.entries.find(e => e.id === snapshot.client_player_id); + const visibleTopEntries = snapshot.entries.slice(0, VISIBLE_TOP_N); + const playerInTopN = playerEntry ? visibleTopEntries.some(e => e.id === playerEntry.id) : false; + const playerPosition = playerEntry + ? snapshot.entries.findIndex(e => e.id === playerEntry.id) + 1 + : null; + + return { + topRows, + playerEntry, + playerInTopN, + playerPosition, + }; + }, [snapshot]); + + const precision = useMemo(() => { + if (!snapshot || topRows.length === 0) return 0; + const percentages = topRows.map((r) => r.territory_percent); + return calculatePrecision(percentages); + }, [snapshot, topRows]); + + // Dynamically calculate container height based on actual row height + useEffect(() => { + if (!tableRef.current || topRows.length === 0) return; + + // Wait for next frame to ensure rows are rendered + requestAnimationFrame(() => { + const firstRow = tableRef.current?.querySelector('tr'); + if (firstRow) { + const rowHeight = firstRow.getBoundingClientRect().height; + // 8 visible rows with small buffer + setContainerHeight(rowHeight * VISIBLE_TOP_N + 1); + } + }); + }, [topRows.length]); + + return ( +
onNationHover(null)}> +
setCollapsed((c) => !c)}> + Leaderboard + {collapsed ? : } +
+ {!collapsed && ( +
+ {status === "ready" && snapshot ? ( + <> +
+ + + {topRows.map((r) => { + const position = snapshot.entries.findIndex(e => e.id === r.id) + 1; + const isPlayer = r.id === snapshot.client_player_id; + const isEliminated = r.territory_percent === 0; + const isHighlighted = highlightedNation === r.id; + return ( + !isEliminated && console.log("select", r.id)} + onMouseEnter={() => !isEliminated && onNationHover(r.id)} + onMouseLeave={() => !isEliminated && onNationHover(null)} + > + + + + + + ); + })} + +
{isEliminated ? '' : position} +
+
+ {r.name} +
+
{isEliminated ? '—' : `${(r.territory_percent * 100).toFixed(precision)}%`}{isEliminated ? '—' : r.troops.toLocaleString()}
+
+ + {!playerInTopN && playerEntry && ( +
+ + + playerEntry.territory_percent > 0 && console.log("select", playerEntry.id)} + onMouseEnter={() => playerEntry.territory_percent > 0 && onNationHover(playerEntry.id)} + onMouseLeave={() => playerEntry.territory_percent > 0 && onNationHover(null)} + > + + + + + + +
{playerEntry.territory_percent === 0 ? '' : playerPosition} +
+
+ {playerEntry.name} +
+
{playerEntry.territory_percent === 0 ? '—' : `${(playerEntry.territory_percent * 100).toFixed(precision)}%`}{playerEntry.territory_percent === 0 ? '—' : playerEntry.troops.toLocaleString()}
+
+ )} + + ) : ( +
+ {status === "loading" && "Loading leaderboard…"} + {status === "waiting" && "Waiting for updates…"} + {status === "error" && "Error loading leaderboard"} +
+ )} +
+ )} +
+ ); +} diff --git a/frontend/src/shared/components/MenuScreen.css b/frontend/src/shared/components/MenuScreen.css new file mode 100644 index 0000000..77d9950 --- /dev/null +++ b/frontend/src/shared/components/MenuScreen.css @@ -0,0 +1,469 @@ +.menu-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.5s ease-out; + opacity: 1; + pointer-events: auto; + user-select: none; +} + +.menu-screen.fade-out { + opacity: 0; + pointer-events: none; +} + +.menu-background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%); + z-index: -1; + overflow: hidden; +} + +.background-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + filter: blur(1px) brightness(0.65) sepia(0.3) contrast(1.5); + transform: scale(1.05); +} + +.background-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(4px); +} + +.menu-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + z-index: 1; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: auto; + overflow-y: auto; + overflow-x: hidden; + padding-top: 0; +} + +.title-section { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 2rem 0; + margin-top: 2rem; + background: rgba(20, 20, 20, 0.45); + backdrop-filter: blur(8px) contrast(1.5); + pointer-events: none; +} + +.game-title { + font-family: "Oswald Variable", sans-serif; + text-transform: uppercase; + font-size: 6rem; + font-weight: 600; + user-select: none; + color: #ffffff; + margin: 0; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); + line-height: 0.8; + animation: shimmer 3s ease-in-out infinite; +} + +.game-subtitle { + padding-top: 0.5rem; + font-family: "Inter Variable", sans-serif; + font-size: 1.3rem; + color: #7e7e7e; + display: none; + margin: 0; + user-select: none; + font-weight: 400; +} + +.menu-islands { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 1.05rem; + width: 700px; + max-width: 90%; + margin: 3rem auto; + pointer-events: auto; +} + +.island-button { + border: none; + border-radius: 5px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + user-select: none; + padding: 0.5rem 1rem; + text-align: left; + + box-shadow: 0 12px 15px rgba(0, 0, 0, 0.175); + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + + &.island-local, + &.island-multiplayer { + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + } +} + +.island-button:hover:not(:disabled) { + transform: translateY(-4px); + box-shadow: 0 12px 15px rgba(0, 0, 0, 0.27); +} + +.island-button:active:not(:disabled) { + transform: translateY(-2px); +} + +.island-button:disabled { + cursor: default; +} + +.island-content { + position: relative; + z-index: 10; +} + +.island-title { + font-family: "Oswald Variable", sans-serif; + font-size: 2rem; + font-weight: 700; + margin: 0 0 0.2rem 0; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.island-description { + font-family: "Inter Variable", sans-serif; + font-size: 0.95rem; + font-weight: 400; + margin: 0; + line-height: 1.4; + opacity: 0.95; +} + +.island-local { + background: #c77a06; + color: #ffffff; + aspect-ratio: 1 / 2; + position: relative; +} + +.island-local:active { + background: #a66305; +} + +.island-local .island-background-image { + position: absolute; + bottom: 0%; + left: 0%; + transform: scale(1) rotate(-0deg); + filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.45)); + width: 100%; + height: auto; + object-fit: contain; + pointer-events: none; + z-index: 0; + image-rendering: pixelated; +} + +.island-right-column { + display: flex; + flex-direction: column; + gap: 1.05rem; + aspect-ratio: 1 / 1; +} + +.island-multiplayer { + background: #4591c0; + color: #ffffff; + flex: 1; + position: relative; +} + +.island-multiplayer:active { + background: #3c7397; +} + +.island-multiplayer .island-background-image { + position: absolute; + bottom: 0%; + right: 0%; + transform: scale(1) rotate(-0deg); + filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.45)); + width: 100%; + height: auto; + object-fit: contain; + pointer-events: none; + z-index: 0; + image-rendering: pixelated; +} + +.island-settings { + background: #f1ebdb; + color: #424c4a; + flex: 1; + position: relative; +} + +.island-settings:active { + background: #d9d1bb; +} + +.island-settings .island-background-image { + position: absolute; + bottom: 0%; + right: 0%; + transform: scale(1) rotate(-0deg); + filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.45)); + width: 100%; + height: auto; + object-fit: contain; + pointer-events: none; + z-index: 0; + image-rendering: pixelated; +} + +.island-downloads { + background: #f1ebdb; + color: #424c4a; + grid-column: 1 / -1; + position: relative; +} + +.island-downloads:active { + background: #d9d1bb; +} + +@keyframes shimmer { + 0%, + 100% { + color: #ffffff; + text-shadow: + 0 2px 4px rgba(0, 0, 0, 0.1), + 0 0 10px rgba(30, 41, 59, 0.2); + } + 50% { + color: #ececec; + text-shadow: + 0 2px 4px rgba(0, 0, 0, 0.1), + 0 0 15px rgba(51, 65, 85, 0.3); + } +} + +/* Responsive design */ +@media (max-width: 768px) { + .game-title { + font-size: 4rem; + } + + .game-subtitle { + font-size: 1rem; + } + + .menu-islands { + grid-template-columns: 1fr; + width: 95%; + padding: 1.5rem 0; + } + + .island-local { + grid-row: auto; + aspect-ratio: auto; + } + + .island-button { + padding: 2rem; + } + + .island-title { + font-size: 2rem; + } + + .island-description { + font-size: 0.95rem; + } +} + +.menu-footer { + position: fixed; + bottom: 1.6rem; + left: 0; + right: 0; + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 0 3rem 0 2rem; + font-family: "Inter Variable", sans-serif; + pointer-events: none; + z-index: 100; +} + +.footer-left { + display: flex; + align-items: flex-end; +} + +.footer-right { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.exit-button { + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + outline: none; + box-shadow: none; + cursor: pointer; + transition: all 0.2s ease; + color: #ffffff; + opacity: 0.85; + mix-blend-mode: screen; + padding: 0; + pointer-events: auto; + filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.3)); +} + +.exit-button:hover { + opacity: 1; + transform: scale(1.1); + background: transparent; + filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.4)); +} + +.exit-button:active { + transform: scale(0.9); + background: transparent; +} + +.exit-button:focus { + outline: none; + background: transparent; +} + +.exit-button:focus-visible { + outline: 2px solid rgba(255, 255, 255, 0.5); + outline-offset: 4px; + background: transparent; +} + +.copyright { + font-size: 1rem; + color: #e2e8f0; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8); + pointer-events: auto; + user-select: none; +} + +.copyright a { + color: #f1f5f9; + text-decoration: none; + transition: color 0.2s ease; +} + +.copyright a:hover { + color: #ffffff; +} + +.version-warning-button { + font-family: "Oswald Variable", sans-serif; + font-size: 1.8rem; + font-weight: 700; + padding: 0; + background: none; + color: #ffffff; + border: none; + cursor: pointer; + transition: all 0.2s ease; + pointer-events: auto; + user-select: none; + text-transform: uppercase; + letter-spacing: 0.01em; + display: inline-block; + position: relative; + box-shadow: none; + + &:active { + background-color: transparent; + } + + &, + span { + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8); + } + + .version-warning-button-asterisk { + color: #ffc56d; + position: absolute; + right: -0.42em; + top: 0; + } +} + +.version-warning-button svg { + flex-shrink: 0; +} + +.version-warning-button:hover { + transform: translateY(-1px); +} + +.version-warning-button:active { + transform: translateY(0); +} + +@media (max-width: 768px) { + .menu-footer { + padding: 0 1.5rem; + bottom: 1.5rem; + } + + .copyright { + font-size: 0.95rem; + } + + .version-warning-button { + font-size: 1.4rem; + } + + .version-warning-button svg { + width: 22px; + height: 22px; + } +} diff --git a/frontend/src/shared/components/MenuScreen.tsx b/frontend/src/shared/components/MenuScreen.tsx new file mode 100644 index 0000000..1dde839 --- /dev/null +++ b/frontend/src/shared/components/MenuScreen.tsx @@ -0,0 +1,119 @@ +import { useState, useEffect, lazy, Suspense } from "react"; +import "overlayscrollbars/overlayscrollbars.css"; +import "./MenuScreen.css"; +import inlineScreenshot from "@/assets/screenshot.png?w=100&format=webp&inline&imagetools"; +import singleplayerImage from "@/assets/singleplayer.png"; +import multiplayerImage from "@/assets/multiplayer.png"; +import settingsImage from "@/assets/settings.png"; +import { Power } from "lucide-react"; +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; + +const AlphaWarningModal = lazy(() => import("./AlphaWarningModal")); + +interface MenuScreenProps { + onStartSingleplayer: () => void; + onExit: () => void; +} + +export function MenuScreen({ onStartSingleplayer, onExit }: MenuScreenProps) { + const [isTransitioning, setIsTransitioning] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Prefetch modal component after delay + useEffect(() => { + const timer = setTimeout(() => { + import("./AlphaWarningModal"); + }, 2000); + return () => clearTimeout(timer); + }, []); + + const handleSingleplayerClick = () => { + setIsTransitioning(true); + // Start the fade out animation + setTimeout(() => { + onStartSingleplayer(); + }, 500); // Match the CSS transition duration + }; + + return ( +
+ ); +} diff --git a/frontend/src/shared/components/SpawnPhaseOverlay.css b/frontend/src/shared/components/SpawnPhaseOverlay.css new file mode 100644 index 0000000..ae32706 --- /dev/null +++ b/frontend/src/shared/components/SpawnPhaseOverlay.css @@ -0,0 +1,67 @@ +.spawn-phase-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 100; +} + +/* Timeout bar - centered, shrinks from both edges */ +.spawn-timeout-bar-container { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 87.5%; + max-width: 900px; + height: 8px; + background: rgba(0, 0, 0, 0.5); + border-radius: 0 0 4px 4px; + overflow: hidden; +} + +.spawn-timeout-bar-fill { + height: 100%; + background: linear-gradient(90deg, #4caf50, #8bc34a); + border-radius: 0 0 4px 4px; + margin: 0 auto; /* Center the fill so it shrinks from both edges */ +} + +/* Instructions - top center */ +.spawn-instructions { + position: absolute; + top: 60px; + left: 50%; + transform: translateX(-50%); + text-align: center; + user-select: none; + pointer-events: none; +} + +.spawn-title { + margin: 0; + font-family: "Oswald Variable", sans-serif; + font-size: 48px; + line-height: 0.85; + font-weight: 700; + color: white; + text-shadow: + 2px 2px 5px rgba(0, 0, 0, 0.6), + 0 0 20px rgba(0, 0, 0, 0.4); + letter-spacing: 1px; +} + +.spawn-subtitle { + font-family: "Inter Variable", sans-serif; + font-weight: 500; + margin: 12px 0 0; + font-size: 18px; + color: rgba(255, 255, 255, 1); + text-shadow: + 1px 1px 1px rgba(0, 0, 0, 0.6), + 0 0 12px rgba(0, 0, 0, 0.4); + max-width: 600px; + line-height: 1.5; +} diff --git a/frontend/src/shared/components/SpawnPhaseOverlay.tsx b/frontend/src/shared/components/SpawnPhaseOverlay.tsx new file mode 100644 index 0000000..502c56d --- /dev/null +++ b/frontend/src/shared/components/SpawnPhaseOverlay.tsx @@ -0,0 +1,57 @@ +import { useState, useEffect } from "react"; +import "./SpawnPhaseOverlay.css"; + +interface SpawnPhaseOverlayProps { + isVisible: boolean; + countdown: { startedAtMs: number; durationSecs: number } | null; +} + +export function SpawnPhaseOverlay({ isVisible, countdown }: SpawnPhaseOverlayProps) { + const [progress, setProgress] = useState(1.0); // 1.0 = full, 0.0 = empty + + useEffect(() => { + if (!isVisible || !countdown) { + setProgress(1.0); + return; + } + + const animate = () => { + const nowMs = Date.now(); + const elapsedMs = nowMs - countdown.startedAtMs; + const elapsedSecs = elapsedMs / 1000; + const remaining = Math.max(0, countdown.durationSecs - elapsedSecs); + const newProgress = remaining / countdown.durationSecs; + + setProgress(newProgress); + + if (newProgress > 0) { + requestAnimationFrame(animate); + } + }; + + const frameId = requestAnimationFrame(animate); + return () => cancelAnimationFrame(frameId); + }, [isVisible, countdown]); + + // Hide overlay when explicitly hidden or when countdown reaches 0 + if (!isVisible || progress <= 0) return null; + + return ( +
+ {/* Timeout bar at top */} +
+
+
+ + {/* Instructions */} +
+

Pick Your Spawn

+

+ Click anywhere on the map to place your starting territory. +
+ You can change your mind before the timer expires. +

+
+
+ ); +} diff --git a/frontend/src/shared/hooks/index.ts b/frontend/src/shared/hooks/index.ts new file mode 100644 index 0000000..1e7e9a3 --- /dev/null +++ b/frontend/src/shared/hooks/index.ts @@ -0,0 +1 @@ +export { useThrottledCallback } from "./useThrottledCallback"; diff --git a/frontend/src/shared/hooks/useThrottledCallback.ts b/frontend/src/shared/hooks/useThrottledCallback.ts new file mode 100644 index 0000000..504ab7f --- /dev/null +++ b/frontend/src/shared/hooks/useThrottledCallback.ts @@ -0,0 +1,75 @@ +import { useCallback, useRef } from "react"; + +/** + * Creates a throttled callback with trailing edge behavior. + * Ensures the last call is executed after the throttle period elapses. + * + * @param callback - The function to throttle + * @param delay - The throttle delay in milliseconds + * @returns A throttled version of the callback + */ +export function useThrottledCallback void>( + callback: T, + delay: number +): T { + const lastCallTimeRef = useRef(0); + const pendingTimeoutRef = useRef(null); + const callbackRef = useRef(callback); + + // Keep callback ref updated + callbackRef.current = callback; + + const throttledCallback = useCallback( + (...args: Parameters) => { + const now = Date.now(); + const timeSinceLastCall = now - lastCallTimeRef.current; + + const executeCallback = () => { + callbackRef.current(...args); + }; + + if (timeSinceLastCall >= delay) { + // Execute immediately if throttle period has elapsed + executeCallback(); + lastCallTimeRef.current = now; + + // Clear any pending timeout since we just executed + if (pendingTimeoutRef.current !== null) { + clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + } else { + // Schedule execution after the remaining throttle period + if (pendingTimeoutRef.current !== null) { + clearTimeout(pendingTimeoutRef.current); + } + + const remainingTime = delay - timeSinceLastCall; + pendingTimeoutRef.current = window.setTimeout(() => { + executeCallback(); + lastCallTimeRef.current = Date.now(); + pendingTimeoutRef.current = null; + }, remainingTime); + } + }, + [delay] + ) as T; + + // Cleanup on unmount + const cleanupRef = useRef(() => { + if (pendingTimeoutRef.current !== null) { + clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + }); + + // Update cleanup function + cleanupRef.current = () => { + if (pendingTimeoutRef.current !== null) { + clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + }; + + return throttledCallback; +} diff --git a/frontend/src/shared/render/CameraController.ts b/frontend/src/shared/render/CameraController.ts new file mode 100644 index 0000000..2ec01bf --- /dev/null +++ b/frontend/src/shared/render/CameraController.ts @@ -0,0 +1,377 @@ +import { Container } from "pixi.js"; + +export interface CameraStateListener { + onCameraUpdate(x: number, y: number, zoom: number): void; +} + +export class CameraController { + private container: Container; + private canvas: HTMLCanvasElement; + private isDragging = false; + private lastMouseX = 0; + private lastMouseY = 0; + private mouseDownX = 0; + private mouseDownY = 0; + private hasDragged = false; + private scale = 1; + private minScale = 0.5; + private maxScale = 13.5; + private listener?: CameraStateListener; + + // Zoom sensitivity configuration + private readonly BASE_ZOOM_SENSITIVITY = 0.15; // Base: 10% zoom per scroll + private readonly ZOOM_IN_SENSITIVITY = 1.1; // 90% of base (10% less sensitive) + private readonly ZOOM_OUT_SENSITIVITY = 0.9; // 120% of base (20% more sensitive) + + // Smooth interpolation state + private targetX = 0; + private targetY = 0; + private targetScale = 1; + private lerpFactor = 0.15; // Smoothness factor (0.1-0.2 is good range) + private isAnimating = false; + + // Animation loop control + private animationFrameId: number | null = null; + + // Event listener references for cleanup + private wheelHandler?: (e: WheelEvent) => void; + private mouseDownHandler?: (e: MouseEvent) => void; + private mouseMoveHandler?: (e: MouseEvent) => void; + private mouseUpHandler?: () => void; + + // Track if window listeners are attached (during drag) + private windowListenersAttached = false; + + // Throttle camera state updates to reduce IPC overhead + private lastListenerNotifyTime = 0; + private readonly LISTENER_NOTIFY_THROTTLE_MS = 100; // Max 10 updates/second + + constructor(container: Container, canvas: HTMLCanvasElement) { + this.container = container; + this.canvas = canvas; + this.setupEventListeners(canvas); + this.startAnimationLoop(); + } + + setStateListener(listener: CameraStateListener) { + this.listener = listener; + } + + private notifyListener(force: boolean = false) { + if (!this.listener) return; + + // Throttle notifications to reduce IPC overhead (unless forced) + const now = Date.now(); + if (!force && now - this.lastListenerNotifyTime < this.LISTENER_NOTIFY_THROTTLE_MS) { + return; + } + this.lastListenerNotifyTime = now; + + this.listener.onCameraUpdate(this.container.x, this.container.y, this.scale); + } + + private startAnimationLoop() { + // Animation loop is started on demand, not continuously + // This is handled by updateSmooth() which requests next frame if needed + } + + private lerp(current: number, target: number, factor: number): number { + return current + (target - current) * factor; + } + + private updateSmooth() { + const threshold = 0.01; // Stop animating when close enough + + // Interpolate position + const oldX = this.container.x; + const oldY = this.container.y; + this.container.x = this.lerp(this.container.x, this.targetX, this.lerpFactor); + this.container.y = this.lerp(this.container.y, this.targetY, this.lerpFactor); + + // Interpolate scale + const oldScale = this.scale; + this.scale = this.lerp(this.scale, this.targetScale, this.lerpFactor); + this.container.scale.set(this.scale); + + // Check if we're close enough to stop animating + const posChanged = Math.abs(this.container.x - oldX) > threshold || Math.abs(this.container.y - oldY) > threshold; + const scaleChanged = Math.abs(this.scale - oldScale) > threshold; + + if (posChanged || scaleChanged) { + if (!this.isAnimating) { + this.isAnimating = true; + } + this.notifyListener(); + // Continue animation + this.animationFrameId = requestAnimationFrame(() => this.updateSmooth()); + } else if (this.isAnimating) { + this.isAnimating = false; + // Snap to final position + this.container.x = this.targetX; + this.container.y = this.targetY; + this.scale = this.targetScale; + this.container.scale.set(this.scale); + this.notifyListener(true); // Force final update + // Animation stopped - no need to request next frame + this.animationFrameId = null; + } + } + + private ensureAnimationRunning() { + // Start animation loop if not already running + if (this.animationFrameId === null) { + this.animationFrameId = requestAnimationFrame(() => this.updateSmooth()); + } + } + + private setupEventListeners(canvas: HTMLCanvasElement) { + // Mouse wheel zoom + this.wheelHandler = (e: WheelEvent) => { + // Skip zoom when shift is pressed (reserved for attack controls) + if (e.shiftKey) { + return; + } + + e.preventDefault(); + + const delta = -e.deltaY; + const scaleFactor = + delta > 0 + ? 1 + this.BASE_ZOOM_SENSITIVITY * this.ZOOM_IN_SENSITIVITY // Zooming in: base * 0.9 + : 1 - this.BASE_ZOOM_SENSITIVITY * this.ZOOM_OUT_SENSITIVITY; // Zooming out: base * 1.2 + const newScale = this.targetScale * scaleFactor; + + if (newScale >= this.minScale && newScale <= this.maxScale) { + // Get mouse position relative to canvas + const rect = canvas.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + // Calculate world position before zoom using TARGET scale + const worldX = (mouseX - this.targetX) / this.targetScale; + const worldY = (mouseY - this.targetY) / this.targetScale; + + // Update target scale + this.targetScale = newScale; + + // Adjust target position to keep mouse point stable + this.targetX = mouseX - worldX * this.targetScale; + this.targetY = mouseY - worldY * this.targetScale; + + // Ensure animation loop is running + this.ensureAnimationRunning(); + } + }; + + // Mouse down - prepare for potential drag + this.mouseDownHandler = (e: MouseEvent) => { + if (e.button === 0) { + // Left mouse button only + this.isDragging = true; + this.hasDragged = false; + this.lastMouseX = e.clientX; + this.lastMouseY = e.clientY; + this.mouseDownX = e.clientX; + this.mouseDownY = e.clientY; + + // Attach window-level listeners to track mouse even over UI elements + this.attachWindowListeners(); + } + }; + + // Mouse move - pan + this.mouseMoveHandler = (e: MouseEvent) => { + if (this.isDragging) { + const dx = e.clientX - this.lastMouseX; + const dy = e.clientY - this.lastMouseY; + + // Check if user has moved enough to count as a drag (3px threshold) + const totalMoved = Math.abs(e.clientX - this.mouseDownX) + Math.abs(e.clientY - this.mouseDownY); + if (totalMoved > 3 && !this.hasDragged) { + this.hasDragged = true; + canvas.style.cursor = "grabbing"; + } + + // Update target position for smooth dragging + this.targetX += dx; + this.targetY += dy; + + // Also update current position directly for responsive feel during drag + this.container.x += dx; + this.container.y += dy; + + this.lastMouseX = e.clientX; + this.lastMouseY = e.clientY; + } + }; + + // Mouse up - stop dragging + const stopDragging = () => { + if (this.isDragging) { + this.isDragging = false; + canvas.style.cursor = "default"; + this.notifyListener(true); // Force update when user stops dragging + + // Remove window-level listeners + this.detachWindowListeners(); + } + }; + + this.mouseUpHandler = stopDragging; + + canvas.addEventListener("wheel", this.wheelHandler); + canvas.addEventListener("mousedown", this.mouseDownHandler); + } + + // Attach mousemove and mouseup to window during drag + private attachWindowListeners() { + if (this.windowListenersAttached) return; + + if (this.mouseMoveHandler) { + window.addEventListener("mousemove", this.mouseMoveHandler); + } + if (this.mouseUpHandler) { + window.addEventListener("mouseup", this.mouseUpHandler); + } + + this.windowListenersAttached = true; + } + + // Detach window listeners when drag ends + private detachWindowListeners() { + if (!this.windowListenersAttached) return; + + if (this.mouseMoveHandler) { + window.removeEventListener("mousemove", this.mouseMoveHandler); + } + if (this.mouseUpHandler) { + window.removeEventListener("mouseup", this.mouseUpHandler); + } + + this.windowListenersAttached = false; + } + + // Check if the last interaction was a drag (for click filtering) + public hadCameraInteraction(): boolean { + return this.hasDragged; + } + + // Public methods for backend control + centerOnTile(tileX: number, tileY: number, tileSize: number, animate: boolean = false) { + // Convert tile coordinates to world position (center of tile) + const worldX = (tileX + 0.5) * tileSize; + const worldY = (tileY + 0.5) * tileSize; + + // Center the camera on the world position + const newX = this.canvas.width / 2 - worldX * this.scale; + const newY = this.canvas.height / 2 - worldY * this.scale; + + if (animate) { + // Smooth animation - set target + this.targetX = newX; + this.targetY = newY; + this.ensureAnimationRunning(); + } else { + // Instant - set both current and target + this.container.x = newX; + this.container.y = newY; + this.targetX = newX; + this.targetY = newY; + this.notifyListener(true); // Force update for programmatic camera control + } + } + + setZoom(zoom: number, animate: boolean = false) { + const newScale = Math.max(this.minScale, Math.min(this.maxScale, zoom)); + + if (animate) { + // Smooth animation - set target + this.targetScale = newScale; + this.ensureAnimationRunning(); + } else { + // Instant - set both current and target + this.scale = newScale; + this.targetScale = newScale; + this.container.scale.set(this.scale); + this.notifyListener(true); // Force update for programmatic camera control + } + } + + panBy(dx: number, dy: number, animate: boolean = false) { + if (animate) { + // Smooth animation - add to target + this.targetX += dx; + this.targetY += dy; + this.ensureAnimationRunning(); + } else { + // Instant - add to both current and target + this.container.x += dx; + this.container.y += dy; + this.targetX += dx; + this.targetY += dy; + this.notifyListener(true); // Force update for programmatic camera control + } + } + + reset() { + this.scale = 1; + this.targetScale = 1; + this.container.scale.set(1); + this.container.x = 0; + this.container.y = 0; + this.targetX = 0; + this.targetY = 0; + this.notifyListener(true); // Force update on reset + } + + // Convert screen coordinates to world coordinates + screenToWorld(screenX: number, screenY: number): { x: number; y: number } { + const worldX = (screenX - this.container.x) / this.scale; + const worldY = (screenY - this.container.y) / this.scale; + return { x: worldX, y: worldY }; + } + + // Convert world coordinates to screen coordinates + worldToScreen(worldX: number, worldY: number): { x: number; y: number } { + const screenX = worldX * this.scale + this.container.x; + const screenY = worldY * this.scale + this.container.y; + return { x: screenX, y: screenY }; + } + + getState() { + return { + x: this.container.x, + y: this.container.y, + zoom: this.scale, + }; + } + + destroy() { + // Reset cursor + this.canvas.style.cursor = "default"; + + // Stop animation loop + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = null; + } + + // Remove window listeners if attached + this.detachWindowListeners(); + + // Remove canvas event listeners + if (this.wheelHandler) { + this.canvas.removeEventListener("wheel", this.wheelHandler); + } + if (this.mouseDownHandler) { + this.canvas.removeEventListener("mousedown", this.mouseDownHandler); + } + + // Clear references + this.wheelHandler = undefined; + this.mouseDownHandler = undefined; + this.mouseMoveHandler = undefined; + this.mouseUpHandler = undefined; + this.listener = undefined; + } +} diff --git a/frontend/src/shared/render/GameRenderer.ts b/frontend/src/shared/render/GameRenderer.ts new file mode 100644 index 0000000..b4db74c --- /dev/null +++ b/frontend/src/shared/render/GameRenderer.ts @@ -0,0 +1,477 @@ +import { Application, Container, Sprite, Texture, BufferImageSource, WebGPURenderer, Renderer, WebGLRenderer } from "pixi.js"; +import { CameraController, CameraStateListener } from "@/shared/render/CameraController"; +import { TerritoryLayer } from "@/shared/render/TerritoryLayer"; +import { ShipLayer } from "@/shared/render/ShipLayer"; +import type { ShipsUpdatePayload } from "@/shared/api/types"; + +export interface RendererConfig { + mapWidth: number; + mapHeight: number; + tileSize: number; +} + +export interface TileChange { + index: number; + owner_id: number; +} + +export interface TerrainData { + width: number; + height: number; + terrain_data: Uint8Array | number[]; // Tile type IDs (u8 values from backend) +} + +export interface PaletteData { + colors: Array<{ r: number; g: number; b: number }>; +} + +export interface TerrainPaletteData { + colors: Array<{ r: number; g: number; b: number }>; +} + +export enum TerrainType { + Water = 0, + Land = 1, + Mountain = 2, +} + +export class GameRenderer { + private app!: Application; + private mainContainer!: Container; + private layersContainer!: Container; + + private terrainLayer!: Container; + private territoryLayer?: TerritoryLayer; + private shipLayer?: ShipLayer; + + // Camera + private cameraController!: CameraController; + + // Map dimensions (set when terrain data is loaded) + private mapWidth: number = 0; + private mapHeight: number = 0; + private tileSize: number = 1; + + private isInitialized = false; + + // Terrain data + private terrainTexture?: Texture; + private terrainSprite?: Sprite; + private terrainPalette: Array<{ r: number; g: number; b: number }> = []; + + // Window resize handler reference + private resizeHandler?: () => void; + + constructor() { + // No config needed - dimensions will be set from terrain data + } + + async init(canvas: HTMLCanvasElement) { + // Validate canvas dimensions + const width = canvas.clientWidth || canvas.width || 800; + const height = canvas.clientHeight || canvas.height || 600; + + if (width === 0 || height === 0) { + throw new Error( + `Canvas has invalid dimensions: ${width}x${height}. ` + `Ensure the canvas element and its parent have explicit dimensions.`, + ); + } + + // Initialize Pixi application + this.app = new Application(); + await this.app.init({ + canvas, + width, + height, + backgroundColor: 0x1a1a2e, // Dark blue-grey background + antialias: false, + resolution: window.devicePixelRatio || 1, + autoDensity: true, + }); + + // Verify initialization succeeded + if (!this.app.stage) { + throw new Error("PixiJS Application failed to initialize - stage is null"); + } + + // Create main container + this.mainContainer = new Container(); + this.app.stage.addChild(this.mainContainer); + + // Create layers container (this gets transformed by camera) + this.layersContainer = new Container(); + this.layersContainer.sortableChildren = true; // Enable z-index sorting + this.mainContainer.addChild(this.layersContainer); + + // 1. Terrain layer (water/land/mountains) - created on init + this.terrainLayer = new Container(); + this.terrainLayer.zIndex = 0; // Bottom layer + this.layersContainer.addChild(this.terrainLayer); + + // 2. Territory colors layer - will be created when terrain data arrives + // (will be added with zIndex = 1 when created) + + // Initialize camera controller + this.cameraController = new CameraController(this.layersContainer, canvas); + + // Handle window resize + this.resizeHandler = () => this.handleResize(); + window.addEventListener("resize", this.resizeHandler); + + // Add ticker for ship interpolation updates + this.app.ticker.add((ticker) => { + if (this.shipLayer) { + // deltaTime is in frames (60fps = 1.0), convert to ticks + // Assuming 60fps and 10 ticks/sec, deltaTime * (10/60) = deltaTime / 6 + const tickDelta = ticker.deltaTime / 6; + this.shipLayer.update(tickDelta); + } + }); + + // Mark as initialized + this.isInitialized = true; + } + + private handleResize() { + const canvas = this.app.canvas as HTMLCanvasElement; + const parent = canvas.parentElement; + if (parent) { + const width = parent.clientWidth; + const height = parent.clientHeight; + + this.app.renderer.resize(width, height); + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + } + } + + // Set terrain palette colors + setTerrainPalette(palette: TerrainPaletteData): void { + this.terrainPalette = palette.colors; + } + + // Initialize terrain from backend data + initTerrain(data: TerrainData): void { + if (!this.isInitialized) { + console.error("FATAL: Renderer not initialized - aborting terrain init"); + throw new Error("Renderer not initialized"); + } + + if (!this.app) { + console.error("FATAL: PixiJS app is null"); + throw new Error("PixiJS app is null"); + } + + if (!this.app.renderer) { + console.error("FATAL: PixiJS renderer is null"); + throw new Error("PixiJS renderer is null"); + } + + if (this.terrainPalette.length === 0) { + console.error("FATAL: Terrain palette not set - call setTerrainPalette first"); + throw new Error("Terrain palette not set"); + } + + const { width, height, terrain_data } = data; + + // Set map dimensions from terrain data + this.mapWidth = width; + this.mapHeight = height; + + // Normalize terrain_data to Uint8Array for consistent handling + const normalizedTerrainData = terrain_data instanceof Uint8Array ? terrain_data : new Uint8Array(terrain_data); + + // Create territory layer if it doesn't exist + if (!this.territoryLayer) { + this.territoryLayer = new TerritoryLayer(this.mapWidth, this.mapHeight, this.tileSize); + this.territoryLayer.container.zIndex = 1; // Above terrain layer + this.layersContainer.addChild(this.territoryLayer.container); + } + + // Create ship layer if it doesn't exist + if (!this.shipLayer) { + this.shipLayer = new ShipLayer(this.mapWidth, this.mapHeight, this.tileSize); + this.shipLayer.container.zIndex = 2; // Above territory layer + this.layersContainer.addChild(this.shipLayer.container); + } + + // Create terrain texture from tile type IDs + const imageData = new Uint8Array(width * height * 4); // RGBA + + for (let i = 0; i < width * height; i++) { + const tileTypeId = normalizedTerrainData[i]; + + // Get color from terrain palette using tile type ID as index + const color = this.terrainPalette[tileTypeId] || { + r: 100, + g: 100, + b: 100, + }; + + const pixelIndex = i * 4; + imageData[pixelIndex] = color.r; + imageData[pixelIndex + 1] = color.g; + imageData[pixelIndex + 2] = color.b; + imageData[pixelIndex + 3] = 255; // Alpha + } + + // Clean up old terrain texture if it exists + if (this.terrainTexture) { + this.terrainTexture.destroy(true); // Destroy texture and base texture + this.terrainTexture = undefined; + } + + // Clean up old terrain sprite if it exists + if (this.terrainSprite) { + this.terrainSprite.destroy(); + this.terrainSprite = undefined; + } + + // Clear terrain layer to prevent duplicate sprites + this.terrainLayer.removeChildren(); + + const bufferSource = new BufferImageSource({ + resource: imageData, + width, + height, + }); + + // Create the texture + this.terrainTexture = new Texture({ source: bufferSource }); + + // Use nearest neighbor filtering for crisp pixel edges + this.terrainTexture.source.scaleMode = "nearest"; + + // Mark source as needing update + bufferSource.update(); + + // Create sprite with the texture (now guaranteed to be uploaded) + this.terrainSprite = new Sprite(this.terrainTexture); + + // Set dimensions explicitly (don't use width/height setters which can cause issues) + this.terrainSprite.scale.set( + (width * this.tileSize) / this.terrainTexture.width, + (height * this.tileSize) / this.terrainTexture.height, + ); + + // Center the terrain + this.terrainSprite.anchor.set(0.5, 0.5); + this.terrainSprite.x = 0; + this.terrainSprite.y = 0; + + // Ensure sprite is visible and rendered + this.terrainSprite.alpha = 1.0; + this.terrainSprite.visible = true; + this.terrainSprite.renderable = true; + + // Disable culling to prevent disappearing at certain zoom levels + this.terrainSprite.cullable = false; + + this.terrainLayer.addChild(this.terrainSprite); + + // Force an immediate render to ensure texture is processed + if (this.app.renderer) { + this.app.renderer.render(this.app.stage); + } + } + + // Initialize palette for territory colors + initPalette(data: PaletteData) { + if (!this.territoryLayer) { + console.error("Cannot initialize palette - territory layer not created yet"); + throw new Error("Territory layer not initialized"); + } + this.territoryLayer.setPalette(data.colors); + + // Also set palette for ship layer if it exists + if (this.shipLayer) { + this.shipLayer.setPalette(data.colors); + } + } + + // Apply full territory snapshot (initial state - sparse format) + applyTerritorySnapshot(_turn: number, territories: Array<{ index: number; owner_id: number }>) { + if (!this.territoryLayer) { + console.error("Cannot apply territory snapshot - territory layer not created yet"); + return; + } + this.territoryLayer.applySnapshot(territories); + } + + // Update territory ownership + updateTerritoryDelta(_turn: number, changes: TileChange[]) { + if (!this.territoryLayer) { + console.error("Cannot update territory delta - territory layer not created yet"); + return; + } + this.territoryLayer.applyDelta(changes); + } + + // Update ships with new variant-based system + updateShips(data: ShipsUpdatePayload) { + if (!this.shipLayer) { + console.error("Cannot update ships - ship layer not created yet"); + return; + } + this.shipLayer.processUpdates(data.updates); + } + + // Handle binary territory delta (for Tauri) + updateBinaryDelta(data: Uint8Array) { + // Decode binary format: [turn:8][count:4][changes...] + if (data.length < 12) { + return; + } + + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + const turn = view.getBigUint64(0, true); + const count = view.getUint32(8, true); + + const expectedSize = 12 + count * 6; + if (data.length !== expectedSize) { + console.error(`Invalid binary delta size: expected ${expectedSize}, got ${data.length}`); + return; + } + + const changes: TileChange[] = []; + for (let i = 0; i < count; i++) { + const offset = 12 + i * 6; + const index = view.getUint32(offset, true); + const owner_id = view.getUint16(offset + 4, true); + changes.push({ index, owner_id }); + } + + this.updateTerritoryDelta(Number(turn), changes); + } + + // Camera control methods + setCameraListener(listener: CameraStateListener) { + this.cameraController.setStateListener(listener); + } + + centerOnTile(tileIndex: number, animate: boolean = false) { + const x = tileIndex % this.mapWidth; + const y = Math.floor(tileIndex / this.mapWidth); + this.cameraController.centerOnTile(x, y, this.tileSize, animate); + } + + setZoom(zoom: number, animate: boolean = false) { + this.cameraController.setZoom(zoom, animate); + } + + panBy(dx: number, dy: number, animate: boolean = false) { + this.cameraController.panBy(dx, dy, animate); + } + + getCameraState() { + return this.cameraController.getState(); + } + + // Check if the last interaction was a camera drag + hadCameraInteraction(): boolean { + return this.cameraController.hadCameraInteraction(); + } + + // Convert screen position to tile index + screenToTile(screenX: number, screenY: number): number | null { + const worldPos = this.cameraController.screenToWorld(screenX, screenY); + + // Adjust for centered map + const adjustedX = worldPos.x + (this.mapWidth * this.tileSize) / 2; + const adjustedY = worldPos.y + (this.mapHeight * this.tileSize) / 2; + + const tileX = Math.floor(adjustedX / this.tileSize); + const tileY = Math.floor(adjustedY / this.tileSize); + + if (tileX >= 0 && tileX < this.mapWidth && tileY >= 0 && tileY < this.mapHeight) { + return tileY * this.mapWidth + tileX; + } + + return null; + } + + // Convert tile index to world position + tileToWorld(tileIndex: number): { x: number; y: number } { + const x = tileIndex % this.mapWidth; + const y = Math.floor(tileIndex / this.mapWidth); + + // Center of tile in world coordinates + const worldX = (x + 0.5) * this.tileSize - (this.mapWidth * this.tileSize) / 2; + const worldY = (y + 0.5) * this.tileSize - (this.mapHeight * this.tileSize) / 2; + + return { x: worldX, y: worldY }; + } + + // Get nation ID at a tile index + getNationAtTile(tileIndex: number | null): number | null { + if (tileIndex === null || !this.territoryLayer) { + return null; + } + + const tileX = tileIndex % this.mapWidth; + const tileY = Math.floor(tileIndex / this.mapWidth); + const nationId = this.territoryLayer.getOwnerAt(tileX, tileY); + + // Nation IDs: 0-65533 (valid), 65534 (unclaimed), 65535 (water) + return nationId < 65534 ? nationId : null; + } + + // Set highlighted nation + setHighlightedNation(nationId: number | null) { + this.territoryLayer?.setHighlightedNation(nationId); + } + + // Get renderer information for analytics + getRendererInfo(): { + renderer: string; + gpu_vendor?: string; + gpu_device?: string; + } { + if (!this.app || !this.app.renderer) { + return { + renderer: "unknown", + }; + } + + const renderer = this.app.renderer; + + const isWebGLRenderer = (renderer: Renderer): renderer is WebGLRenderer => { + return (renderer as any).gl != undefined; + }; + + const isWebGPURenderer = (renderer: Renderer): renderer is WebGPURenderer => { + return (renderer as any).gpu != undefined; + }; + + let gpuVendor: string | undefined; + let gpuDevice: string | undefined; + let rendererName = "unknown"; + + // Try to extract GPU info from WebGPU adapter + if (isWebGPURenderer(renderer)) { + const gpuAdapter = renderer.gpu?.adapter; + gpuVendor = gpuAdapter.info?.vendor; + gpuDevice = gpuAdapter.info?.device; + rendererName = "webgpu"; + } + // Fallback to WebGL renderer info + else if (isWebGLRenderer(renderer)) { + const gl = (renderer as WebGLRenderer).gl; + const debugInfo = gl.getExtension("WEBGL_debug_renderer_info"); + if (debugInfo) { + gpuVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL); + gpuDevice = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); + } + rendererName = "webgl"; + } + + return { + renderer: rendererName, + gpu_vendor: gpuVendor, + gpu_device: gpuDevice, + }; + } + + // Clean up + destroy() {} +} diff --git a/frontend/src/shared/render/ShipLayer.ts b/frontend/src/shared/render/ShipLayer.ts new file mode 100644 index 0000000..fa3ffc0 --- /dev/null +++ b/frontend/src/shared/render/ShipLayer.ts @@ -0,0 +1,201 @@ +import { Container, Graphics } from "pixi.js"; +import type { ShipUpdateVariant } from "@/shared/api/types"; + +// Must match backend SHIP_TICKS_PER_TILE constant +const SHIP_TICKS_PER_TILE = 1; + +interface ShipState { + owner_id: number; + path: number[]; + current_path_index: number; + ticks_until_move: number; + troops: number; + sprite: Container; +} + +export class ShipLayer { + public container: Container; + private ships: Map; // Map of ship ID to ship state + private palette: Array<{ r: number; g: number; b: number }> = []; + private mapWidth: number; + private mapHeight: number; + private tileSize: number; + + constructor(mapWidth: number, mapHeight: number, tileSize: number) { + this.container = new Container(); + this.container.zIndex = 2; // Above territory layer + this.ships = new Map(); + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; + this.tileSize = tileSize; + } + + /** + * Set the color palette for nations + */ + setPalette(colors: Array<{ r: number; g: number; b: number }>) { + this.palette = colors; + } + + /** + * Process ship update variants (Create/Move/Destroy) + */ + processUpdates(updates: ShipUpdateVariant[]) { + for (const update of updates) { + switch (update.type) { + case "Create": + this.createShip(update); + break; + case "Move": + this.moveShip(update); + break; + case "Destroy": + this.destroyShip(update); + break; + } + } + } + + /** + * Create a new ship with full state + */ + private createShip(update: Extract) { + const sprite = this.createShipSprite(update.owner_id, update.troops); + + const shipState: ShipState = { + owner_id: update.owner_id, + path: update.path, + current_path_index: 0, + ticks_until_move: SHIP_TICKS_PER_TILE, + troops: update.troops, + sprite, + }; + + this.ships.set(update.id, shipState); + this.container.addChild(sprite); + + // Position at start of path + this.updateShipPosition(shipState); + } + + /** + * Update ship to next tile in path + */ + private moveShip(update: Extract) { + const ship = this.ships.get(update.id); + if (!ship) return; + + ship.current_path_index = update.current_path_index; + ship.ticks_until_move = SHIP_TICKS_PER_TILE; + + this.updateShipPosition(ship); + } + + /** + * Remove ship from map + */ + private destroyShip(update: Extract) { + const ship = this.ships.get(update.id); + if (!ship) return; + + this.container.removeChild(ship.sprite); + ship.sprite.destroy({ children: true }); + this.ships.delete(update.id); + } + + /** + * Create a ship sprite (circle with logarithmic size scaling) + */ + private createShipSprite(owner_id: number, troops: number): Container { + const container = new Container(); + + const color = this.palette[owner_id] || { r: 128, g: 128, b: 128 }; + const hexColor = (color.r << 16) | (color.g << 8) | color.b; + + const graphics = new Graphics(); + + // Logarithmic size scaling based on troop count + const baseSize = 0.15 * this.tileSize; // Min size for small troops + const maxSize = 0.5 * this.tileSize; // Max size for large troops + // Scale: 1 troop = baseSize, 1000 troops ā‰ˆ maxSize + const size = baseSize + (Math.log10(troops + 1) / Math.log10(1001)) * (maxSize - baseSize); + + // Draw circle (no rotation needed) + graphics.circle(0, 0, size); + graphics.fill({ color: hexColor, alpha: 0.9 }); + + // Add border + graphics.circle(0, 0, size); + graphics.stroke({ width: 1, color: 0x000000, alpha: 0.5 }); + + container.addChild(graphics); + return container; + } + + /** + * Update ship position with interpolation + */ + private updateShipPosition(ship: ShipState) { + const currentTile = ship.path[ship.current_path_index]; + const nextTile = ship.path[ship.current_path_index + 1]; + + // Calculate current tile position + const currentX = currentTile % this.mapWidth; + const currentY = Math.floor(currentTile / this.mapWidth); + + // World position of current tile + let worldX = (currentX + 0.5) * this.tileSize - (this.mapWidth * this.tileSize) / 2; + let worldY = (currentY + 0.5) * this.tileSize - (this.mapHeight * this.tileSize) / 2; + + // Interpolate to next tile if available + if (nextTile !== undefined) { + const nextX = nextTile % this.mapWidth; + const nextY = Math.floor(nextTile / this.mapWidth); + + const nextWorldX = (nextX + 0.5) * this.tileSize - (this.mapWidth * this.tileSize) / 2; + const nextWorldY = (nextY + 0.5) * this.tileSize - (this.mapHeight * this.tileSize) / 2; + + // Calculate interpolation progress (0 to 1) + const progress = 1.0 - ship.ticks_until_move / SHIP_TICKS_PER_TILE; + + // Lerp between current and next position + worldX = worldX + (nextWorldX - worldX) * progress; + worldY = worldY + (nextWorldY - worldY) * progress; + } + + ship.sprite.x = worldX; + ship.sprite.y = worldY; + } + + /** + * Update interpolation for all ships (called each render frame) + */ + update(deltaTime: number) { + for (const ship of this.ships.values()) { + // Decrement ticks_until_move locally + ship.ticks_until_move = Math.max(0, ship.ticks_until_move - deltaTime); + + // Update visual position + this.updateShipPosition(ship); + } + } + + /** + * Clear all ships + */ + clear() { + for (const ship of this.ships.values()) { + this.container.removeChild(ship.sprite); + ship.sprite.destroy({ children: true }); + } + this.ships.clear(); + } + + /** + * Destroy the layer and clean up resources + */ + destroy() { + this.clear(); + this.container.destroy(); + } +} diff --git a/frontend/src/shared/render/TerritoryLayer.ts b/frontend/src/shared/render/TerritoryLayer.ts new file mode 100644 index 0000000..9637fb5 --- /dev/null +++ b/frontend/src/shared/render/TerritoryLayer.ts @@ -0,0 +1,412 @@ +import { Container, Sprite, Texture, BufferImageSource, Filter, GlProgram } from "pixi.js"; + +export interface RgbColor { + r: number; + g: number; + b: number; +} + +// Vertex shader - PixiJS v8 standard filter vertex shader (GLSL 100 ES) +const TERRITORY_VERTEX_SHADER = ` +attribute vec2 aPosition; + +varying vec2 vTextureCoord; +varying vec2 vFilterCoord; + +uniform vec4 uInputSize; +uniform vec4 uOutputFrame; +uniform vec4 uOutputTexture; + +vec4 filterVertexPosition() { + vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy; + position.x = position.x * (2.0 / uOutputTexture.x) - 1.0; + position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z; + return vec4(position, 0.0, 1.0); +} + +vec2 filterTextureCoord() { + return aPosition * (uOutputFrame.zw * uInputSize.zw); +} + +void main() { + gl_Position = filterVertexPosition(); + vTextureCoord = filterTextureCoord(); + vFilterCoord = vTextureCoord; +} +`; + +const TERRITORY_FRAGMENT_SHADER = ` +precision mediump float; + +varying vec2 vTextureCoord; + +uniform sampler2D uTexture; +uniform vec2 uMapSize; +uniform sampler2D uPalette; +uniform float uHighlightedNation; + +// Decode u16 from RG8 (R=low byte, G=high byte) +float decodeU16(vec2 rg) { + return rg.r * 255.0 + rg.g * 65280.0; +} + +void main() { + vec4 center = texture2D(uTexture, vTextureCoord); + float centerOwner = decodeU16(center.rg); + + // Unclaimed = 65534, Water = 65535 + if (centerOwner >= 65534.0) { + discard; + } + + vec2 texelSize = 1.0 / uMapSize; + + bool isBorder = false; + for (float dist = 1.0; dist <= 2.0; dist += 1.0) { + vec2 offset = texelSize * dist; + + float left = decodeU16(texture2D(uTexture, vTextureCoord + vec2(-offset.x, 0.0)).rg); + float right = decodeU16(texture2D(uTexture, vTextureCoord + vec2(offset.x, 0.0)).rg); + float top = decodeU16(texture2D(uTexture, vTextureCoord + vec2(0.0, -offset.y)).rg); + float bottom = decodeU16(texture2D(uTexture, vTextureCoord + vec2(0.0, offset.y)).rg); + + if (centerOwner != left || centerOwner != right || + centerOwner != top || centerOwner != bottom) { + isBorder = true; + break; + } + } + + // Map nation ID to 2D palette texture (256x256 grid = 65536 colors) + // X = nation_id % 256, Y = nation_id / 256 + float x = mod(centerOwner, 256.0) / 256.0; + float y = floor(centerOwner / 256.0) / 256.0; + vec4 territoryColor = texture2D(uPalette, vec2(x, y)); + + // Highlighting logic + bool isHighlighting = uHighlightedNation >= 0.0; + bool isHighlighted = (isHighlighting && centerOwner == uHighlightedNation); + + float alpha = isBorder ? 0.85 : 0.60; + + if (isBorder) { + if (isHighlighted) { + territoryColor.rgb = vec3(255, 255, 255); + } else { + territoryColor.rgb *= 0.85; + } + } + + gl_FragColor = vec4(territoryColor.rgb, alpha); +} +`; + +export class TerritoryLayer { + public container: Container; + + private sprite!: Sprite; + private texture!: Texture; + private bufferSource!: BufferImageSource; + + private ownerData: Uint16Array; + private textureData: Uint8Array; + + private paletteTexture?: Texture; + private paletteSource?: BufferImageSource; + private paletteData: Uint8Array; + + // Shader/filter + private territoryFilter?: Filter; + + // Dirty region tracking for partial updates + private isDirty: boolean = false; + private dirtyMinX: number = 0; + private dirtyMinY: number = 0; + private dirtyMaxX: number = 0; + private dirtyMaxY: number = 0; + + // Batching state + private pendingChanges: Array<{ index: number; owner_id: number }> = []; + private updateScheduled: boolean = false; + + private width: number; + private height: number; + private tileSize: number; + + constructor(width: number, height: number, tileSize: number) { + this.width = width; + this.height = height; + this.tileSize = tileSize; + + this.container = new Container(); + + this.ownerData = new Uint16Array(width * height); + this.textureData = new Uint8Array(width * height * 2); + // Support up to 65536 players (u16::MAX + 1) + this.paletteData = new Uint8Array(65536 * 4); + + this.initTexture(); + } + + private initTexture() { + // RG8: R=low byte, G=high byte + this.textureData.fill(0); + + this.bufferSource = new BufferImageSource({ + resource: this.textureData, + width: this.width, + height: this.height, + format: "rg8unorm", + }); + + this.texture = new Texture({ source: this.bufferSource }); + this.texture.source.scaleMode = "nearest"; + this.texture.source.update(); + + this.sprite = new Sprite(this.texture); + this.sprite.scale.set((this.width * this.tileSize) / this.texture.width, (this.height * this.tileSize) / this.texture.height); + this.sprite.anchor.set(0.5, 0.5); + this.sprite.x = 0; + this.sprite.y = 0; + this.sprite.alpha = 1.0; + this.sprite.visible = true; + this.sprite.renderable = true; + this.sprite.cullable = false; + this.sprite.blendMode = "screen"; + + this.container.addChild(this.sprite); + } + + setPalette(colors: Array) { + // Support up to 65536 players (u16::MAX + 1) + for (let i = 0; i < Math.min(colors.length, 65536); i++) { + const color = colors[i]; + const pixelIndex = i * 4; + this.paletteData[pixelIndex] = color.r; + this.paletteData[pixelIndex + 1] = color.g; + this.paletteData[pixelIndex + 2] = color.b; + this.paletteData[pixelIndex + 3] = 255; + } + + if (!this.paletteSource) { + // Use 256x256 2D texture (65536 total colors) to avoid WebGL texture size limits + this.paletteSource = new BufferImageSource({ + resource: this.paletteData, + width: 256, + height: 256, + }); + this.paletteTexture = new Texture({ source: this.paletteSource }); + } else { + this.paletteSource.update(); + } + + this.setupShader(); + } + + applySnapshot(territories: Array<{ index: number; owner_id: number }>) { + // Clear all tiles to unclaimed (0) + this.ownerData.fill(0); + this.textureData.fill(0); + + // Apply claimed tiles from sparse snapshot + for (const tile of territories) { + const { index, owner_id } = tile; + + if (index >= 0 && index < this.ownerData.length) { + this.ownerData[index] = owner_id; + + const texIndex = index * 2; + this.textureData[texIndex] = owner_id & 0xff; + this.textureData[texIndex + 1] = (owner_id >> 8) & 0xff; + } + } + + this.isDirty = true; + this.dirtyMinX = 0; + this.dirtyMinY = 0; + this.dirtyMaxX = this.width - 1; + this.dirtyMaxY = this.height - 1; + + this.updateTexture(); + } + + applyDelta(changes: Array<{ index: number; owner_id: number }>) { + if (changes.length === 0) { + return; + } + + this.pendingChanges.push(...changes); + + if (!this.updateScheduled) { + this.updateScheduled = true; + requestAnimationFrame(() => this.processPendingChanges()); + } + } + + private processPendingChanges() { + this.updateScheduled = false; + + if (this.pendingChanges.length === 0) { + return; + } + + const OWNER_WATER = 65535; + + let minX = this.width; + let minY = this.height; + let maxX = 0; + let maxY = 0; + + for (const change of this.pendingChanges) { + if (change.index >= 0 && change.index < this.ownerData.length) { + const value = change.owner_id; + + if (value === OWNER_WATER) { + continue; + } + + this.ownerData[change.index] = value; + + const texIndex = change.index * 2; + this.textureData[texIndex] = value & 0xff; + this.textureData[texIndex + 1] = (value >> 8) & 0xff; + + const x = change.index % this.width; + const y = Math.floor(change.index / this.width); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + + this.pendingChanges = []; + + if (maxX >= minX && maxY >= minY) { + this.isDirty = true; + this.dirtyMinX = minX; + this.dirtyMinY = minY; + this.dirtyMaxX = maxX; + this.dirtyMaxY = maxY; + this.updateTexture(); + } + } + + private updateTexture() { + if (!this.isDirty) { + return; + } + + const imageData = this.bufferSource.resource as Uint8Array; + const gl = (this.texture.source as any)._glTextures?.[0]?.gl; + const glTexture = (this.texture.source as any)._glTextures?.[0]?.texture; + + // If GL context not ready, fall back to full upload + if (!gl || !glTexture) { + this.bufferSource.update(); + this.isDirty = false; + return; + } + + // Calculate dirty region dimensions + const regionWidth = this.dirtyMaxX - this.dirtyMinX + 1; + const regionHeight = this.dirtyMaxY - this.dirtyMinY + 1; + const totalPixels = this.width * this.height; + const dirtyPixels = regionWidth * regionHeight; + + // Use partial update if dirty region is small (< 25% of texture) + if (dirtyPixels < totalPixels * 0.25) { + // Extract dirty region into contiguous buffer (R8 format) + const regionData = new Uint8Array(regionWidth * regionHeight); + + for (let y = 0; y < regionHeight; y++) { + const srcY = this.dirtyMinY + y; + const srcOffset = srcY * this.width + this.dirtyMinX; + const dstOffset = y * regionWidth; + + // Copy one row at a time (single channel) + regionData.set(imageData.subarray(srcOffset, srcOffset + regionWidth), dstOffset); + } + + // Upload dirty region only + gl.bindTexture(gl.TEXTURE_2D, glTexture); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, // mip level + this.dirtyMinX, + this.dirtyMinY, + regionWidth, + regionHeight, + gl.RED, + gl.UNSIGNED_BYTE, + regionData, + ); + } else { + // Dirty region is large, upload full texture + this.bufferSource.update(); + } + + // Clear dirty flag + this.isDirty = false; + } + + private setupShader() { + if (!this.paletteTexture) { + console.warn("Cannot setup shader: palette texture not initialized"); + return; + } + + // Create filter with the territory shader + this.territoryFilter = new Filter({ + glProgram: new GlProgram({ + vertex: TERRITORY_VERTEX_SHADER, + fragment: TERRITORY_FRAGMENT_SHADER, + }), + resources: { + // uTexture is auto-bound to sprite texture + uPalette: this.paletteTexture.source, + territoryUniforms: { + uMapSize: { value: [this.width, this.height], type: "vec2" }, + uHighlightedNation: { value: -1, type: "f32" }, + }, + }, + }); + + // Apply filter to sprite + this.sprite.filters = [this.territoryFilter]; + } + + // Get owner at specific tile + getOwnerAt(tileX: number, tileY: number): number { + if (tileX >= 0 && tileX < this.width && tileY >= 0 && tileY < this.height) { + const index = tileY * this.width + tileX; + return this.ownerData[index]; + } + return 0; + } + + // Set highlighted nation (-1 or null to clear) + setHighlightedNation(nationId: number | null) { + if (this.territoryFilter?.resources?.territoryUniforms) { + this.territoryFilter.resources.territoryUniforms.uniforms.uHighlightedNation = nationId ?? -1; + } + } + + // Clear all territory + clear() { + this.ownerData.fill(0); + const imageData = this.bufferSource.resource as Uint8Array; + + // Clear all pixels + imageData.fill(0); + + // Mark entire texture as dirty + this.isDirty = true; + this.dirtyMinX = 0; + this.dirtyMinY = 0; + this.dirtyMaxX = this.width - 1; + this.dirtyMaxY = this.height - 1; + + this.updateTexture(); + } +} diff --git a/frontend/src/shared/render/index.ts b/frontend/src/shared/render/index.ts new file mode 100644 index 0000000..acce340 --- /dev/null +++ b/frontend/src/shared/render/index.ts @@ -0,0 +1,2 @@ +export { CameraController, type CameraStateListener } from "./CameraController"; +export { GameRenderer } from "./GameRenderer"; diff --git a/frontend/src/shared/utils/binaryDecoding.ts b/frontend/src/shared/utils/binaryDecoding.ts new file mode 100644 index 0000000..a654e55 --- /dev/null +++ b/frontend/src/shared/utils/binaryDecoding.ts @@ -0,0 +1,59 @@ +/** + * Binary decoding utilities for territory snapshot data from Rust backend. + * + * The TerritorySnapshot uses a sparse binary format to minimize data transfer: + * - Only claimed tiles (owner_id != 0) are transmitted + * - All other tiles default to unclaimed (owner_id = 0) + * - Binary format: [count:4][tile_changes...] + * where each change is [index:4][owner:2] (6 bytes per claimed tile) + */ + +export interface TileOwnership { + index: number; + owner_id: number; +} + +/** + * Decode sparse binary territory snapshot from Rust backend. + * + * @param data Binary data array (serialized from Vec in Rust) + * @returns Array of claimed tiles with indices and owner IDs, or null if invalid + */ +export function decodeTerritorySnapshot(data: number[] | Uint8Array): TileOwnership[] | null { + // Convert to Uint8Array if needed for faster access + const bytes = data instanceof Uint8Array ? data : new Uint8Array(data); + + if (bytes.length < 4) { + console.error("Invalid territory snapshot: not enough data for count"); + return null; + } + + // Use DataView for faster little-endian reads + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + + // Read count (4 bytes, little-endian u32) + const count = view.getUint32(0, true); + + const expectedSize = 4 + count * 6; + if (bytes.length !== expectedSize) { + console.error( + `Invalid territory snapshot: expected ${expectedSize} bytes, got ${bytes.length}` + ); + return null; + } + + const tiles: TileOwnership[] = new Array(count); + for (let i = 0; i < count; i++) { + const offset = 4 + i * 6; + + // Read index (4 bytes, little-endian u32) + const index = view.getUint32(offset, true); + + // Read owner_id (2 bytes, little-endian u16) + const owner_id = view.getUint16(offset + 4, true); + + tiles[i] = { index, owner_id }; + } + + return tiles; +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..103d8a0 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,17 @@ +/// + +declare const __DESKTOP__: boolean; +declare const __APP_VERSION__: string; +declare const __GIT_COMMIT__: string; +declare const __BUILD_TIME__: string; + +declare module "@wasm/borders.js" { + export { default } from "../pkg/borders"; + export * from "../pkg/borders"; +} + +// vite-imagetools support +declare module "*&imagetools" { + const out: any; + export default out; +} diff --git a/frontend/tsconfig.browser.json b/frontend/tsconfig.browser.json new file mode 100644 index 0000000..05eeba7 --- /dev/null +++ b/frontend/tsconfig.browser.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/browser/**/*"] +} diff --git a/frontend/tsconfig.desktop.json b/frontend/tsconfig.desktop.json new file mode 100644 index 0000000..0d35454 --- /dev/null +++ b/frontend/tsconfig.desktop.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/desktop/**/*"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..71ef7df --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@wasm/*": ["pkg/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/shared/**/*", "src/*", "src/assets/**/*", "pages/**/*"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..e4e81d8 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,127 @@ +import { defineConfig, HmrContext } from "vite"; +import react from "@vitejs/plugin-react"; +import vike from "vike/plugin"; +import { resolve } from "path"; +import { readFileSync, existsSync } from "fs"; +import { execSync } from "child_process"; +import { imagetools } from "vite-imagetools"; + +const host = process.env.TAURI_DEV_HOST; + +// Read version from workspace Cargo.toml +function getVersionFromCargoToml(): string { + const cargoToml = readFileSync(resolve(__dirname, "../Cargo.toml"), "utf-8"); + const versionMatch = cargoToml.match(/^\[workspace\.package\][\s\S]*?^version\s*=\s*"(.+?)"$/m); + if (!versionMatch) { + throw new Error("Failed to find version in workspace Cargo.toml"); + } + return versionMatch[1]; +} + +// Read git commit from .source-commit file or git command +function getGitCommit(): string { + const sourceCommitPath = resolve(__dirname, "../.source-commit"); + if (existsSync(sourceCommitPath)) { + return readFileSync(sourceCommitPath, "utf-8").trim(); + } + + // Fallback to git command for local development + try { + return execSync("git rev-parse HEAD", { encoding: "utf-8", cwd: resolve(__dirname, "..") }).trim(); + } catch { + return "unknown"; + } +} + +// Get current build time in UTC (ISO 8601 format) +function getBuildTime(): string { + return new Date().toISOString(); +} + +const fullReloadAlways = { + name: "full-reload-always", + handleHotUpdate(context: HmrContext): void { + context.server.ws.send({ type: "full-reload" }); + }, +}; + +// https://vite.dev/config/ +export default defineConfig(({ mode }) => ({ + base: process.env.GITHUB_PAGES === "true" ? "/borde.rs/" : "/", + css: { + transformer: "lightningcss", + }, + plugins: [vike(), react(), imagetools(), fullReloadAlways], + + define: { + __DESKTOP__: JSON.stringify(mode !== "browser"), + __APP_VERSION__: JSON.stringify(getVersionFromCargoToml()), + __GIT_COMMIT__: JSON.stringify(getGitCommit()), + __BUILD_TIME__: JSON.stringify(getBuildTime()), + }, + + resolve: { + alias: { + "@": resolve(__dirname, "src"), + "@wasm": resolve(__dirname, "pkg"), + }, + }, + + build: { + outDir: mode === "browser" ? "dist/browser" : "dist", + cssMinify: "lightningcss", + rollupOptions: { + output: { + // manualChunks: (id) => { + // // console.log(id); + // if (id.includes("node_modules")) { + // // React core - always needed + // if (id.includes("react") || id.includes("react-dom")) { + // return "vendor-react"; + // } + // // PixiJS - large rendering library + // if (id.includes("pixi")) { + // return "vendor-pixi"; + // } + // // UI libraries - icons, scrollbars, animations + // if (id.includes("radix-ui") || id.includes("lucide-react") || id.includes("overlayscrollbars") || id.includes("motion")) { + // return "vendor-ui"; + // } + // return "vendor-other"; + // } + // }, + }, + }, + }, + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + // Browser mode uses port 1421, desktop mode uses port 1420 + port: mode === "browser" ? 1421 : 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: mode === "browser" ? 1422 : 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/crates/borders-desktop/**"], + }, + // Add headers for SharedArrayBuffer support in browser mode + headers: + mode === "browser" + ? { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + } + : undefined, + }, +})); diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..e918eb3 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +targets = ["wasm32-unknown-unknown"] diff --git a/scripts/release-manager/.env.example b/scripts/release-manager/.env.example new file mode 100644 index 0000000..955e6e2 --- /dev/null +++ b/scripts/release-manager/.env.example @@ -0,0 +1,19 @@ +# GitHub Personal Access Token with 'repo' and 'actions:read' scopes +# Generate at: https://github.com/settings/tokens +GITHUB_TOKEN=ghp_your_token_here + +# GitHub Repository to download artifacts from (format: owner/repo) +# Example: Xevion/my-private-repo +GITHUB_REPOSITORY=owner/repository + +# R2 Bucket name for storing releases +R2_BUCKET_NAME=borders + +# Base URL for downloads (CDN/R2 public URL) +R2_BASE_URL=https://cdn.borders.xevion.dev + +# R2 API Credentials (generate at: https://dash.cloudflare.com/r2/api-tokens) +# After creating an R2 API token, you'll receive these credentials +R2_ACCOUNT_ID=your_account_id_here +R2_ACCESS_KEY_ID=your_access_key_id_here +R2_SECRET_ACCESS_KEY=your_secret_access_key_here diff --git a/scripts/release-manager/.gitignore b/scripts/release-manager/.gitignore new file mode 100644 index 0000000..1c72110 --- /dev/null +++ b/scripts/release-manager/.gitignore @@ -0,0 +1,16 @@ +# Dependencies +node_modules/ +pnpm-lock.yaml + +# Environment variables +.env + +# Build output +dist/ + +# Downloaded releases (should be in repo root) +releases/ + +# Temporary files +*.tmp.json +.manifest.tmp.json diff --git a/scripts/release-manager/bun.lock b/scripts/release-manager/bun.lock new file mode 100644 index 0000000..d6e58f5 --- /dev/null +++ b/scripts/release-manager/bun.lock @@ -0,0 +1,741 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "release-manager", + "dependencies": { + "@actions/artifact": "^2.2.4", + "@aws-sdk/client-s3": "^3.908.0", + "@octokit/rest": "^21.0.2", + "adm-zip": "^0.5.16", + "commander": "^13.0.0", + "dotenv": "^16.4.7", + "glob": "^11.0.0", + "ora": "^8.1.1", + "picocolors": "^1.1.1", + }, + "devDependencies": { + "@types/node": "^22.15.8", + "tsx": "^4.19.2", + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@actions/artifact": ["@actions/artifact@2.3.2", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^5.1.1", "@actions/http-client": "^2.1.0", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^3.5.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request-error": "^5.0.0", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-uX2Mr5KEPcwnzqa0Og9wOTEKIae6C/yx9P/m8bIglzCS5nZDkcQC/zRWjjoEsyVecL6oQpBx5BuqQj/yuVm0gw=="], + + "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], + + "@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="], + + "@actions/github": ["@actions/github@5.1.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "@octokit/core": "^3.6.0", "@octokit/plugin-paginate-rest": "^2.17.0", "@octokit/plugin-rest-endpoint-methods": "^5.13.0" } }, "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g=="], + + "@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], + + "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], + + "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.908.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/credential-provider-node": "3.908.0", "@aws-sdk/middleware-bucket-endpoint": "3.901.0", "@aws-sdk/middleware-expect-continue": "3.901.0", "@aws-sdk/middleware-flexible-checksums": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-location-constraint": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-sdk-s3": "3.908.0", "@aws-sdk/middleware-ssec": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/signature-v4-multi-region": "3.908.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@aws-sdk/xml-builder": "3.901.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/eventstream-serde-browser": "^4.2.0", "@smithy/eventstream-serde-config-resolver": "^4.3.0", "@smithy/eventstream-serde-node": "^4.2.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-blob-browser": "^4.2.1", "@smithy/hash-node": "^4.2.0", "@smithy/hash-stream-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/md5-js": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-c/89iG3of8UEiWbRK014DoHLy8PLLTJtM9IvYLPsvrf83kpV2P/K9WrdbjW4h6e5qt9XPgfNTZ8U607mt7pdmA=="], + + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.908.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PseFMWvtac+Q+zaY9DMISE+2+glNh0ROJ1yR4gMzeafNHSwkdYu4qcgxLWIOnIodGydBv/tQ6nzHPzExXnUUgw=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.908.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@aws-sdk/xml-builder": "3.901.0", "@smithy/core": "^3.15.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/signature-v4": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-okl6FC2cQT1Oidvmnmvyp/IEvqENBagKO0ww4YV5UtBkf0VlhAymCWkZqhovtklsqgq0otag2VRPAgnrMt6nVQ=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-FK2YuxoI5CxUflPOIMbVAwDbi6Xvu+2sXopXLmrHc2PfI39M3vmjEoQwYCP8WuQSRb+TbAP3xAkxHjFSBFR35w=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/node-http-handler": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-stream": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-eLbz0geVW9EykujQNnYfR35Of8MreI6pau5K6XDFDUSWO9GF8wqH7CQwbXpXHBlCTHtq4QSLxzorD8U5CROhUw=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/credential-provider-env": "3.908.0", "@aws-sdk/credential-provider-http": "3.908.0", "@aws-sdk/credential-provider-process": "3.908.0", "@aws-sdk/credential-provider-sso": "3.908.0", "@aws-sdk/credential-provider-web-identity": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-7Cgnv5wabgFtsgr+Uc/76EfPNGyxmbG8aICn3g3D3iJlcO4uuOZI8a77i0afoDdchZrTC6TG6UusS/NAW6zEoQ=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.908.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.908.0", "@aws-sdk/credential-provider-http": "3.908.0", "@aws-sdk/credential-provider-ini": "3.908.0", "@aws-sdk/credential-provider-process": "3.908.0", "@aws-sdk/credential-provider-sso": "3.908.0", "@aws-sdk/credential-provider-web-identity": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-8OKbykpGw5bdfF/pLTf8YfUi1Kl8o1CTjBqWQTsLOkE3Ho3hsp1eQx8Cz4ttrpv0919kb+lox62DgmAOEmTr1w=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-sWnbkGjDPBi6sODUzrAh5BCDpnPw0wpK8UC/hWI13Q8KGfyatAmCBfr+9OeO3+xBHa8N5AskMncr7C4qS846yQ=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.908.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.908.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/token-providers": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-WV/aOzuS6ZZhrkPty6TJ3ZG24iS8NXP0m3GuTVuZ5tKi9Guss31/PJ1CrKPRCYGm15CsIjf+mrUxVnNYv9ap5g=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-9xWrFn6nWlF5KlV4XYW+7E6F33S3wUUEGRZ/+pgDhkIZd527ycT2nPG2dZ3fWUZMlRmzijP20QIJDqEbbGWe1Q=="], + + "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-mPF3N6eZlVs9G8aBSzvtoxR1RZqMo1aIwR+X8BAZSkhfj55fVF2no4IfPXfdFO3I66N+zEQ8nKoB0uTATWrogQ=="], + + "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-bwq9nj6MH38hlJwOY9QXIDwa6lI48UsaZpaXbdD71BljEIRlxDzfB4JaYb+ZNNK7RIAdzsP/K05mJty6KJAQHw=="], + + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.908.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-hYGhNBvdfnxhhywYRkesdxIZD8rvhsp2CBci5kCqrR2o5VvEkn5+waUQtkREtkciEpC4ent4fadg7N9XfTKvgQ=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A=="], + + "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-MuCS5R2ngNoYifkVt05CTULvYVWX0dvRT0/Md4jE3a0u0yMygYy31C1zorwfE/SUgAQXyLmUx8ATmPp9PppImQ=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@aws/lambda-invoke-store": "^0.0.1", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.15.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/signature-v4": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-23MbAOHsGaD0kTVMVLumaIM1f9vtDImIn2lSvPullbjFHKS4XxfrKuPumtKDzl8gzcux+98XnmfDRKH0fzkOUA=="], + + "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-YiLLJmA3RvjL38mFLuu8fhTTGWtp2qT24VqpucgfoyziYcTgIQkJJmKi90Xp6R6/3VcArqilyRgM1+x8i/em+Q=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@smithy/core": "^3.15.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-R0ePEOku72EvyJWy/D0Z5f/Ifpfxa0U9gySO3stpNhOox87XhsILpcIsCHPy0OHz1a7cMoZsF6rMKSzDeCnogQ=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.908.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZxDYrfxOKXNFHLyvJtT96TJ0p4brZOhwRE4csRXrezEVUN+pNgxuem95YvMALPVhlVqON2CTzr8BX+CcBKvX9Q=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.908.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/protocol-http": "^5.3.0", "@smithy/signature-v4": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-8OodflIzZM2GVuCGiGK6hqwsbfHRDl4kQcEYzHRg9p91H4h5Y876DPvLRkwM7pSC7LKUL0XkKWWVVjwJbp6/Ig=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-4SosHWRQ8hj1X2yDenCYHParcCjHcd7S+Mdb/lelwF0JBFCNC+dNCI9ws3cP/dFdZO/AIhJQGUBzEQtieloixw=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.901.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-endpoints": "^3.2.0", "tslib": "^2.6.2" } }, "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.907.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-Hus/2YCQmtCEfr4Ls88d07Q99Ex59uvtktiPTV963Q7w7LHuIT/JBjrbwNxtSm2KlJR9PHNdqxwN+fSuNsMGMQ=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.908.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-l6AEaKUAYarcEy8T8NZ+dNZ00VGLs3fW2Cqu1AuPENaSad0/ahEU+VU7MpXS8FhMRGPgplxKVgCTLyTY0Lbssw=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.901.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.0.1", "", {}, "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw=="], + + "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + + "@azure/core-http-compat": ["@azure/core-http-compat@2.3.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g=="], + + "@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="], + + "@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="], + + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/core-xml": ["@azure/core-xml@1.5.0", "", { "dependencies": { "fast-xml-parser": "^5.0.7", "tslib": "^2.8.1" } }, "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@azure/storage-blob": ["@azure/storage-blob@12.28.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.0.0-beta.2", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-VhQHITXXO03SURhDiGuHhvc/k/sD2WvJUS7hqhiVNbErVCuQoLtWql7r97fleBlIRKHJaa9R7DpBjfE0pfLYcA=="], + + "@azure/storage-common": ["@azure/storage-common@12.1.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-5HZu1stgW5I7cH2LaLlr4x6tk5AlDRGXBfxynPqI6027KwYaS/u1ucOYIrITmu7M+rhqsbUbkuIHQG7oxKN2uQ=="], + + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.9.0", "", {}, "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA=="], + + "@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.9.0", "", { "dependencies": { "@bufbuild/protobuf": "2.9.0", "@typescript/vfs": "^1.5.2", "typescript": "5.4.5" } }, "sha512-uoiwNVYoTq+AyqaV1L6pBazGx5fXOO89L0NSR9/7hEfo0Y8n9T1jsKGu4mkitLmP3z+8gJREaule1mMuKBPyYw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], + + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], + + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@octokit/auth-token": ["@octokit/auth-token@2.5.0", "", { "dependencies": { "@octokit/types": "^6.0.3" } }, "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g=="], + + "@octokit/core": ["@octokit/core@3.6.0", "", { "dependencies": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", "@octokit/request": "^5.6.3", "@octokit/request-error": "^2.0.5", "@octokit/types": "^6.0.3", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q=="], + + "@octokit/endpoint": ["@octokit/endpoint@6.0.12", "", { "dependencies": { "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA=="], + + "@octokit/graphql": ["@octokit/graphql@4.8.0", "", { "dependencies": { "@octokit/request": "^5.6.0", "@octokit/types": "^6.0.3", "universal-user-agent": "^6.0.0" } }, "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg=="], + + "@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="], + + "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.6.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw=="], + + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@1.0.4", "", { "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="], + + "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@13.5.0", "", { "dependencies": { "@octokit/types": "^13.10.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw=="], + + "@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="], + + "@octokit/request": ["@octokit/request@5.6.3", "", { "dependencies": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.1.0", "@octokit/types": "^6.16.1", "is-plain-object": "^5.0.0", "node-fetch": "^2.6.7", "universal-user-agent": "^6.0.0" } }, "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A=="], + + "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], + + "@octokit/rest": ["@octokit/rest@21.1.1", "", { "dependencies": { "@octokit/core": "^6.1.4", "@octokit/plugin-paginate-rest": "^11.4.2", "@octokit/plugin-request-log": "^5.3.1", "@octokit/plugin-rest-endpoint-methods": "^13.3.0" } }, "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg=="], + + "@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="], + + "@protobuf-ts/protoc": ["@protobuf-ts/protoc@2.11.1", "", { "bin": { "protoc": "protoc.js" } }, "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg=="], + + "@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="], + + "@protobuf-ts/runtime-rpc": ["@protobuf-ts/runtime-rpc@2.11.1", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" } }, "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ=="], + + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg=="], + + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="], + + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.3.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ=="], + + "@smithy/core": ["@smithy/core@3.15.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-VJWncXgt+ExNn0U2+Y7UywuATtRYaodGQKFo9mDyh70q+fJGedfrqi2XuKU1BhiLeXgg6RZrW7VEKfeqFhHAJA=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XE7CtKfyxYiNZ5vz7OvyTf1osrdbJfmUy+rbh+NLQmZumMGvY0mT0Cq1qKSfhrvLtRYzMsOBuRpi10dyI0EBPg=="], + + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.0", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-U53p7fcrk27k8irLhOwUu+UYnBqsXNLKl1XevOpsxK3y1Lndk8R7CSiZV6FN3fYFuTPuJy5pP6qa/bjDzEkRvA=="], + + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-uwx54t8W2Yo9Jr3nVF5cNnkAAnMCJ8Wrm+wDlQY6rY/IrEgZS3OqagtCu/9ceIcZFQ1zVW/zbN9dxb5esuojfA=="], + + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.0", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-yjM2L6QGmWgJjVu/IgYd6hMzwm/tf4VFX0lm8/SvGbGBwc+aFl3hOzvO/e9IJ2XI+22Tx1Zg3vRpFRs04SWFcg=="], + + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.0", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-C3jxz6GeRzNyGKhU7oV656ZbuHY93mrfkT12rmjDdZch142ykjn8do+VOkeRNjSGKw01p4g+hdalPYPhmMwk1g=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3AvYYbB+Dv5EPLqnJIAgYw/9+WzeBiUYS8B+rU0pHq5NMQMvrZmevUROS4V2GAt0jEOn9viBzPLrZE+riTNd5Q=="], + + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.1", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-Os9cg1fTXMwuqbvjemELlf+HB5oEeVyZmYsTbAtDQBmjGyibjmbeeqcaw7xOJLIHrkH/u0wAYabNcN6FRTqMRg=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA=="], + + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-8dELAuGv+UEjtzrpMeNBZc1sJhO8GxFVV/Yh21wE35oX4lOE697+lsMHBoUIFAUuYkTMIeu0EuJSEsH7/8Y+UQ=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@smithy/md5-js": ["@smithy/md5-js@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-LFEPniXGKRQArFmDQ3MgArXlClFJMsXDteuQQY8WG1/zzv6gVSo96+qpkuu1oJp4MZsKrwchY0cuAoPKzEbaNA=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.1", "", { "dependencies": { "@smithy/core": "^3.15.0", "@smithy/middleware-serde": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JtM4SjEgImLEJVXdsbvWHYiJ9dtuKE8bqLlvkvGi96LbejDL6qnVpVxEFUximFodoQbg0Gnkyff9EKUhFhVJFw=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/service-error-classification": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-wXxS4ex8cJJteL0PPQmWYkNi9QKDWZIpsndr0wZI2EL+pSSvA/qqxXU60gBOJoIc2YgtZSWY/PE86qhKCCKP1w=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.3.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0" } }, "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.7.1", "", { "dependencies": { "@smithy/core": "^3.15.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-stack": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-stream": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-WXVbiyNf/WOS/RHUoFMkJ6leEVpln5ojCjNBnzoZeMsnCg3A0BRhLK3WYc4V7PmYcYPZh9IYzzAg9XcNSzYxYQ=="], + + "@smithy/types": ["@smithy/types@4.6.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.0", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-H4MAj8j8Yp19Mr7vVtGgi7noJjvjJbsKQJkvNnLlrIFduRFT5jq5Eri1k838YW7rN2g5FTnXpz5ktKVr1KVgPQ=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.1", "", { "dependencies": { "@smithy/config-resolver": "^4.3.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-PuDcgx7/qKEMzV1QFHJ7E4/MMeEjaA7+zS5UNcHCLPvvn59AeZQ0DSDGMpqC2xecfa/1cNGm4l8Ec/VxCuY7Ug=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.0", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.0", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.1", "@smithy/node-http-handler": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-0TD5M5HCGu5diEvZ/O/WquSjhJPasqv7trjoqHyWjNh/FBeBl7a0ztl9uFMOsauYtRfd8jvpzIAQhDHbx+nvZw=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@smithy/util-waiter": ["@smithy/util-waiter@4.2.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-0Z+nxUU4/4T+SL8BCNN4ztKdQjToNvUYmkF1kXO5T7Yz3Gafzh0HeIG6mrkN8Fz3gn9hSyxuAT+6h4vM+iQSBQ=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="], + + "@typescript/vfs": ["@typescript/vfs@1.6.1", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.1", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-SnbaqayTVFEA6/tYumdF0UmybY0KHyKwGPBXnyckFlrrKdhWFrL3a2HIPXHjht5ZOElKGcXfD2D63P36btb+ww=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], + + "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "bare-events": ["bare-events@2.8.0", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], + + "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], + + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], + + "bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="], + + "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + + "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], + + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + + "fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="], + + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + + "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-tsconfig": ["get-tsconfig@4.12.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw=="], + + "glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + + "jwt-decode": ["jwt-decode@3.1.2", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.0", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="], + + "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], + + "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], + + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], + + "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], + + "@actions/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@2.21.3", "", { "dependencies": { "@octokit/types": "^6.40.0" }, "peerDependencies": { "@octokit/core": ">=2" } }, "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw=="], + + "@actions/github/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@5.16.2", "", { "dependencies": { "@octokit/types": "^6.39.0", "deprecation": "^2.3.1" }, "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw=="], + + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.3.0", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-gkWGshjYcQCF+6qtlrqBqELqNqnt4CxruY6UVAWWnqb3DQ6qaNFEIKqzYep1XzHLM/QtrHVCxyPOtTk4LTQ7Aw=="], + + "@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@octokit/core/@octokit/request-error": ["@octokit/request-error@2.1.0", "", { "dependencies": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg=="], + + "@octokit/plugin-paginate-rest/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="], + + "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/request/@octokit/request-error": ["@octokit/request-error@2.1.0", "", { "dependencies": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg=="], + + "@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], + + "@octokit/rest/@octokit/core": ["@octokit/core@6.1.6", "", { "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", "@octokit/request": "^9.2.3", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "before-after-hook": "^3.0.2", "universal-user-agent": "^7.0.0" } }, "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA=="], + + "@octokit/rest/@octokit/plugin-request-log": ["@octokit/plugin-request-log@5.3.1", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw=="], + + "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], + + "archiver-utils/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + + "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], + + "@octokit/plugin-paginate-rest/@octokit/core/before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + + "@octokit/plugin-paginate-rest/@octokit/core/universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + + "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + + "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], + + "@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@5.1.2", "", {}, "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="], + + "@octokit/rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@8.2.2", "", { "dependencies": { "@octokit/request": "^9.2.3", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA=="], + + "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@9.2.4", "", { "dependencies": { "@octokit/endpoint": "^10.1.4", "@octokit/request-error": "^6.1.8", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^2.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA=="], + + "@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@6.1.8", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ=="], + + "@octokit/rest/@octokit/core/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="], + + "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@3.0.2", "", {}, "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="], + + "@octokit/rest/@octokit/core/universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + + "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], + + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], + + "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@10.1.4", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA=="], + + "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="], + + "archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + } +} diff --git a/scripts/release-manager/cli.ts b/scripts/release-manager/cli.ts new file mode 100644 index 0000000..ce4930b --- /dev/null +++ b/scripts/release-manager/cli.ts @@ -0,0 +1,605 @@ +#!/usr/bin/env node +import { createHash } from "crypto"; +import { mkdir, readFile, stat, unlink, writeFile } from "fs/promises"; +import { basename, resolve } from "path"; +import { program } from "commander"; +import { glob } from "glob"; +import { Octokit } from "@octokit/rest"; +import { Upload } from "@aws-sdk/lib-storage"; +import ora from "ora"; +import pc from "picocolors"; +import "dotenv/config"; +import { + createR2Client, + getReleasesDir, + loadManifest, + createManifest, + saveManifest, + padWithColor, + constrainString, + formatRelativeTime, +} from "./utils.js"; +import type { + ArtifactMapping, + Checksums, + FileType, + Manifest, + Platform, + ReleaseFile, + Version, +} from "./types.js"; + +const ARTIFACT_MAPPINGS: ArtifactMapping[] = [ + { + artifactName: "iron-borders-linux-x86_64", + platform: "linux-x86_64", + expectedFiles: [ + { glob: "**/*.AppImage", type: "appimage" }, + { glob: "**/*.deb", type: "deb" }, + { glob: "**/*.rpm", type: "rpm" }, + ], + }, + { + artifactName: "iron-borders-macos-x86_64", + platform: "macos-x86_64", + expectedFiles: [ + { glob: "**/*.dmg", type: "dmg" }, + { glob: "**/*.app", type: "app" }, + ], + }, + { + artifactName: "iron-borders-macos-aarch64", + platform: "macos-aarch64", + expectedFiles: [ + { glob: "**/*.dmg", type: "dmg" }, + { glob: "**/*.app", type: "app" }, + ], + }, + { + artifactName: "iron-borders-windows-x86_64", + platform: "windows-x86_64", + expectedFiles: [{ glob: "**/*.exe", type: "exe" }], + }, + { + artifactName: "iron-borders-windows-aarch64", + platform: "windows-aarch64", + expectedFiles: [{ glob: "**/*.exe", type: "exe" }], + }, +]; + +const artifactNames = ARTIFACT_MAPPINGS.map((m) => m.artifactName); + +function validateEnvironment(): void { + const required = [ + 'GITHUB_TOKEN', + 'GITHUB_REPOSITORY', + 'R2_BUCKET_NAME', + 'R2_BASE_URL', + 'R2_ACCOUNT_ID', + 'R2_ACCESS_KEY_ID', + 'R2_SECRET_ACCESS_KEY', + ]; + + const missing = required.filter((key) => !process.env[key]); + + if (missing.length > 0) { + console.error(pc.red('\nāŒ Missing required environment variables:')); + for (const key of missing) { + console.error(pc.red(` - ${key}`)); + } + console.error( + pc.yellow('\nCheck .env file in scripts/release-manager/.env\n') + ); + process.exit(1); + } +} + +validateEnvironment(); + +async function downloadArtifact( + octokit: Octokit, + owner: string, + repo: string, + artifactId: number, + outputPath: string +): Promise { + const response = await octokit.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id: artifactId, + archive_format: "zip", + }); + + const arrayBuffer = response.data as unknown as ArrayBuffer; + const buffer = Buffer.from(arrayBuffer); + + await mkdir(resolve(outputPath, ".."), { recursive: true }); + await writeFile(outputPath, buffer); +} + +async function extractZip(zipPath: string, outputDir: string): Promise { + const AdmZip = (await import("adm-zip")).default; + const zip = new AdmZip(zipPath); + zip.extractAllTo(outputDir, true); +} + +async function listRecentRuns( + octokit: Octokit, + owner: string, + repo: string +): Promise { + const workflows = await octokit.rest.actions.listRepoWorkflows({ + owner, + repo, + }); + + const buildsWorkflow = workflows.data.workflows.find( + (w) => w.path === ".github/workflows/builds.yml" + ); + + if (!buildsWorkflow) { + throw new Error("builds.yml workflow not found"); + } + + const runs = await octokit.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id: buildsWorkflow.id, + per_page: 10, + }); + + console.log(pc.bold(pc.cyan("\nšŸ“‹ Recent builds workflow runs:\n"))); + console.log( + pc.dim( + `${"Run ID".padEnd(12)} ${"Status".padEnd(12)} ${"Branch".padEnd( + 20 + )} ${"Created"}` + ) + ); + console.log(pc.dim("─".repeat(80))); + + for (const run of runs.data.workflow_runs) { + let status: string; + if (run.status === "completed") { + status = + run.conclusion === "success" + ? pc.green("āœ“ success") + : pc.red(`āœ— ${run.conclusion}`); + } else { + status = pc.yellow(`⋯ ${run.status}`); + } + + const date = formatRelativeTime(new Date(run.created_at)); + const branch = constrainString(run.head_branch || "unknown", 20); + + console.log( + `${String(run.id).padEnd(12)} ${padWithColor(status, 12)} ${branch.padEnd( + 20 + )} ${date}` + ); + } + + console.log(""); +} + +async function computeChecksums(filePath: string): Promise { + const buffer = await readFile(filePath); + + return { + sha256: createHash("sha256").update(buffer).digest("hex"), + sha512: createHash("sha512").update(buffer).digest("hex"), + md5: createHash("md5").update(buffer).digest("hex"), + }; +} + +async function findFiles( + baseDir: string, + artifactName: string, + expectedFiles: { glob: string; type: FileType }[] +): Promise<{ path: string; type: FileType }[]> { + const artifactDir = resolve(baseDir, artifactName); + const results: { path: string; type: FileType }[] = []; + + for (const { glob: pattern, type } of expectedFiles) { + const matches = await glob(pattern, { + cwd: artifactDir, + absolute: true, + nodir: true, + }); + + for (const match of matches) { + results.push({ path: match, type }); + } + } + + return results; +} + +async function uploadToR2( + localPath: string, + remotePath: string, + bucket: string, + s3Client: ReturnType, + onProgress?: (percent: number) => void +): Promise { + try { + const fileContent = await readFile(localPath); + + const upload = new Upload({ + client: s3Client, + params: { + Bucket: bucket, + Key: remotePath, + Body: fileContent, + }, + }); + + if (onProgress) { + upload.on('httpUploadProgress', (progress) => { + if (progress.loaded && progress.total) { + const percent = Math.round((progress.loaded / progress.total) * 100); + onProgress(percent); + } + }); + } + + await upload.done(); + } catch (error: any) { + throw new Error( + `Failed to upload to R2 bucket "${bucket}": ${error.message}\n` + + `Path: ${remotePath}\n` + + `Hint: Check that the bucket exists and your R2 credentials are correct` + ); + } +} + +async function processVersion( + version: string, + bucket: string, + baseUrl: string, + skipUpload: boolean, + s3Client: ReturnType +): Promise { + const releasesDir = getReleasesDir(); + const versionDir = resolve(releasesDir, version); + + const version_obj: Version = { + version, + released: new Date().toISOString(), + visible: true, + platforms: {}, + }; + + for (const mapping of ARTIFACT_MAPPINGS) { + const spinner = ora(`Finding files for ${mapping.platform}`).start(); + + try { + const files = await findFiles( + versionDir, + mapping.artifactName, + mapping.expectedFiles + ); + + if (files.length === 0) { + spinner.warn(pc.yellow(`No files found for ${mapping.platform}`)); + continue; + } + + const releaseFiles: ReleaseFile[] = []; + + for (const { path: filePath, type } of files) { + const filename = basename(filePath); + const remotePath = `releases/v${version}/${mapping.platform}/${filename}`; + + spinner.text = `Computing checksums for ${filename}`; + const checksums = await computeChecksums(filePath); + const fileStats = await stat(filePath); + + if (!skipUpload) { + spinner.text = `Uploading ${filename} (0%)`; + await uploadToR2(filePath, remotePath, bucket, s3Client, (percent) => { + spinner.text = `Uploading ${filename} (${percent}%)`; + }); + } + + releaseFiles.push({ + type, + url: `${baseUrl}/${remotePath}`, + filename, + size: fileStats.size, + checksums, + }); + } + + version_obj.platforms[mapping.platform] = { files: releaseFiles }; + spinner.succeed( + pc.green(`Processed ${mapping.platform} (${releaseFiles.length} files)`) + ); + } catch (error) { + spinner.fail(pc.red(`Failed to process ${mapping.platform}`)); + throw error; + } + } + + return version_obj; +} + +program + .name("release-manager") + .description("Manage Iron Borders releases") + .version("1.0.0"); + +program + .command("download [version] [run-id]") + .description("Download and extract GitHub Actions artifacts") + .action(async (version, runId) => { + try { + const [owner, repo] = process.env.GITHUB_REPOSITORY!.split('/'); + const token = process.env.GITHUB_TOKEN!; + const octokit = new Octokit({ auth: token }); + + if (!version && !runId) { + await listRecentRuns(octokit, owner, repo); + throw new Error( + "No version and run ID specified. Provide both: download " + ); + } + + if (!version || !runId) { + throw new Error( + "Both version and run ID are required: download " + ); + } + + const runIdNum = parseInt(runId, 10); + + const run = await octokit.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: runIdNum, + }); + + if (run.data.status !== "completed") { + throw new Error( + `Cannot download artifacts from run ${runId}: workflow is still ${run.data.status}` + ); + } + + console.log( + pc.bold(pc.cyan(`\nšŸ“¦ Downloading artifacts from run ${runId}\n`)) + ); + + const artifacts = await octokit.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runIdNum, + }); + + const releasesDir = getReleasesDir(); + const versionDir = resolve(releasesDir, version); + await mkdir(versionDir, { recursive: true }); + + for (const artifactName of artifactNames) { + const artifact = artifacts.data.artifacts.find( + (a) => a.name === artifactName + ); + + if (!artifact) { + console.log( + pc.yellow(`āš ļø Artifact ${artifactName} not found, skipping`) + ); + continue; + } + + const spinner = ora(`Downloading ${artifactName}`).start(); + + try { + const zipPath = resolve(versionDir, `${artifactName}.zip`); + await downloadArtifact(octokit, owner, repo, artifact.id, zipPath); + + spinner.text = `Extracting ${artifactName}`; + const extractDir = resolve(versionDir, artifactName); + await extractZip(zipPath, extractDir); + + await unlink(zipPath); + + spinner.succeed(pc.green(`Downloaded and extracted ${artifactName}`)); + } catch (error) { + spinner.fail(pc.red(`Failed to download ${artifactName}`)); + throw error; + } + } + + console.log( + pc.bold(pc.green(`\nāœ… All artifacts downloaded to ${versionDir}\n`)) + ); + } catch (error) { + console.error( + pc.red("\nāŒ Error:"), + error instanceof Error ? error.message : error + ); + process.exit(1); + } + }); + +program + .command("upload ") + .description("Upload release artifacts to R2 and update manifest") + .option("--latest", "Set this version as latest") + .option("--skip-upload", "Skip upload, only generate manifest locally") + .action(async (version, options) => { + try { + const { latest, skipUpload } = options; + const bucket = process.env.R2_BUCKET_NAME!; + const baseUrl = process.env.R2_BASE_URL!; + + console.log(pc.bold(pc.cyan(`\nšŸš€ Uploading release ${version}\n`))); + + const s3Client = createR2Client(); + + const spinner = ora("Loading existing manifest").start(); + let manifest: Manifest; + try { + manifest = await loadManifest(bucket, s3Client); + spinner.succeed("Loaded manifest"); + } catch (error: any) { + if (error.message.includes("not found")) { + spinner.warn("Manifest not found, creating new one"); + manifest = await createManifest(); + } else { + spinner.fail("Failed to load manifest"); + throw error; + } + } + + const versionObj = await processVersion( + version, + bucket, + baseUrl, + skipUpload, + s3Client + ); + + const existingIndex = manifest.versions.findIndex( + (v) => v.version === version + ); + if (existingIndex >= 0) { + manifest.versions[existingIndex] = versionObj; + console.log( + pc.yellow(`\nāš ļø Updated existing version ${version} in manifest`) + ); + } else { + manifest.versions.unshift(versionObj); + console.log(pc.green(`\nāœ… Added new version ${version} to manifest`)); + } + + manifest.versions.sort((a, b) => + b.version.localeCompare(a.version, undefined, { numeric: true }) + ); + + const shouldSetLatest = + latest || !manifest.latest || manifest.latest < version; + if (shouldSetLatest) { + manifest.latest = version; + console.log(pc.cyan(`šŸ“Œ Set ${version} as latest version`)); + } + + if (!skipUpload) { + const manifestSpinner = ora("Uploading manifest").start(); + await saveManifest(manifest, bucket, s3Client); + manifestSpinner.succeed("Uploaded manifest"); + } else { + console.log(pc.yellow("\nāš ļø Skipped upload (--skip-upload flag)")); + console.log("\nGenerated manifest:"); + console.log(JSON.stringify(manifest, null, 2)); + } + + console.log( + pc.bold(pc.green(`\nāœ… Release ${version} uploaded successfully!\n`)) + ); + } catch (error) { + console.error(pc.red("\nāŒ Error:"), error); + process.exit(1); + } + }); + +program + .command("list") + .description("List all available releases") + .action(async () => { + try { + const bucket = process.env.R2_BUCKET_NAME!; + + const s3Client = createR2Client(); + const manifest = await loadManifest(bucket, s3Client); + + console.log(pc.bold(pc.cyan("\nšŸ“¦ Available Releases\n"))); + console.log( + pc.dim( + `${"Version".padEnd(12)} ${"Visible".padEnd(10)} ${"Released".padEnd( + 30 + )} ${"Status"}` + ) + ); + console.log(pc.dim("─".repeat(80))); + + for (const version of manifest.versions) { + const isLatest = version.version === manifest.latest; + const versionStr = isLatest + ? pc.green(version.version) + : version.version; + const visibleStr = version.visible ? pc.green("āœ“ Yes") : pc.red("āœ— No"); + const releasedStr = new Date(version.released).toLocaleString(); + const statusStr = isLatest ? pc.cyan(" (latest)") : ""; + + console.log( + `${padWithColor(versionStr, 12)} ${padWithColor( + visibleStr, + 10 + )} ${releasedStr.padEnd(30)} ${statusStr}` + ); + } + + console.log(""); + } catch (error: any) { + if (error.message.includes("not found")) { + console.error( + pc.red("\nāŒ No releases found (manifest.json does not exist)\n") + ); + process.exit(1); + } + + console.error(pc.red("\nāŒ Error:"), error.message); + process.exit(1); + } + }); + +program + .command("set-latest") + .description("Set a version as the latest in the manifest") + .requiredOption( + "-v, --version ", + "Version to set as latest (e.g., 0.5.7)" + ) + .action(async (options) => { + try { + const { version } = options; + const bucket = process.env.R2_BUCKET_NAME!; + + const s3Client = createR2Client(); + + const spinner = ora("Loading manifest").start(); + const manifest = await loadManifest(bucket, s3Client); + spinner.succeed("Loaded manifest"); + + const versionExists = manifest.versions.some( + (v) => v.version === version + ); + + if (!versionExists) { + console.error(pc.red(`\nāŒ Version ${version} not found in manifest`)); + console.log(pc.yellow("\nAvailable versions:")); + manifest.versions.forEach((v) => { + const indicator = + v.version === manifest.latest ? pc.green(" (current latest)") : ""; + console.log(` - ${v.version}${indicator}`); + }); + process.exit(1); + } + + const previousLatest = manifest.latest; + manifest.latest = version; + + const uploadSpinner = ora("Updating manifest").start(); + await saveManifest(manifest, bucket, s3Client); + uploadSpinner.succeed("Updated manifest"); + + console.log(pc.bold(pc.green(`\nāœ… Latest version updated:`))); + console.log(` ${previousLatest} → ${pc.cyan(version)}\n`); + } catch (error) { + console.error(pc.red("\nāŒ Error:"), error); + process.exit(1); + } + }); + +program.parse(); diff --git a/scripts/release-manager/manifest.json b/scripts/release-manager/manifest.json new file mode 100644 index 0000000..c50b22a --- /dev/null +++ b/scripts/release-manager/manifest.json @@ -0,0 +1,132 @@ +{ + "latest": "0.5.7", + "display_mode": "latest_only", + "versions": [ + { + "version": "0.5.7", + "released": "2025-10-11T16:56:44.403Z", + "visible": true, + "platforms": { + "linux-x86_64": { + "files": [ + { + "type": "appimage", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/linux-x86_64/iron-borders_0.1.0_amd64.AppImage", + "filename": "iron-borders_0.1.0_amd64.AppImage", + "size": 86313464, + "checksums": { + "sha256": "40343d73e0dd47e1af8f31c8f4e84689883982374fbeda8d6b2baf25c9402c52", + "sha512": "ea9e09c35d39b800ed7d837d0b2bb352298d7cff87bbd9fdc4bffed7fcc3ff6f0293efc183f075442692bb2ebc861bfcc234c18938602bc125681fcbe65dfedf", + "md5": "afa799a612aeca1bbbfdcd3226bbcf7f" + } + }, + { + "type": "deb", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/linux-x86_64/iron-borders_0.1.0_amd64.deb", + "filename": "iron-borders_0.1.0_amd64.deb", + "size": 10504424, + "checksums": { + "sha256": "54fd6568d38b457688b509a3cd4096e735d1a8e4174eeaf5c9103dc926f87777", + "sha512": "008bcd687c8dc2f5cbc4c104a25201339c77a3ace1788bf50bc386e2f14a9cf6752d519c4f4b5b8c8cddbe65cb12ca1bded688f14297b977c786f10d455a31fe", + "md5": "cd0128c310616b1ddf906128608ccd0c" + } + }, + { + "type": "rpm", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/linux-x86_64/iron-borders-0.1.0-1.x86_64.rpm", + "filename": "iron-borders-0.1.0-1.x86_64.rpm", + "size": 10504162, + "checksums": { + "sha256": "ff244308de628f2af6b56363516c7aca4b69adb67dc678a5c3bdcfdb16635120", + "sha512": "8aecae09425c98f210c41ddcc3a89c85d4b97dc9065412ba2d7b105cf04e1f3e1cb3f6a8491f9c2fa2539acac4fdec8bc35cddfd6010597e7f928c7b692b503b", + "md5": "be75cfa33ba9ecb44421864e72ea9967" + } + } + ] + }, + "macos-x86_64": { + "files": [ + { + "type": "dmg", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/macos-x86_64/iron-borders_0.1.0_x64.dmg", + "filename": "iron-borders_0.1.0_x64.dmg", + "size": 9602672, + "checksums": { + "sha256": "01f1fb16ae253cdd27234b02e9885a83d02fe1798226cb2d659ac68702824c20", + "sha512": "a704bf2e084eb89768a169005d3fa53ccada1089b676e01cd8525e84b3dff269cc442f7c1d62010850b6e5105648eba532217b3aa60644717baac07266e80ea0", + "md5": "d7a950210a22e88995dbb64618377151" + } + } + ] + }, + "macos-aarch64": { + "files": [ + { + "type": "dmg", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/macos-aarch64/iron-borders_0.1.0_aarch64.dmg", + "filename": "iron-borders_0.1.0_aarch64.dmg", + "size": 9060820, + "checksums": { + "sha256": "43e63e7194e895350603faa6c6272b4cf1ddf0ea4c94769ef0584815ad9f88e5", + "sha512": "b6b9e3c2d871bffdea1de6506ae1b3e5bd5a1b6559f59dc29fda289c7bfc74231fd72f0e8b97f0529e6c08fcbc12f9fe808216ba56c86fb3a48323cd6972817b", + "md5": "c4524a748b7064eb6b1cb1973cf69f27" + } + } + ] + }, + "windows-x86_64": { + "files": [ + { + "type": "exe", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/windows-x86_64/iron-borders_0.1.0_x64-setup.exe", + "filename": "iron-borders_0.1.0_x64-setup.exe", + "size": 6371644, + "checksums": { + "sha256": "3f6f22a7efe09603338631d2fe66ed3ad6b8d1a1120651fcfb14463e429ae1f3", + "sha512": "a6bee3f0f1ca07b27ac63bce56adaf6953299051bb74f09e7d2a44dbb6a2ee5fe064739fc0efd46e117f584b7a5a13b2e99d8ba8219ccba3dd255c5f5926c6e8", + "md5": "977b59994347542a475847ddef9c1622" + } + }, + { + "type": "msi", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/windows-x86_64/iron-borders_0.1.0_x64_en-US.msi", + "filename": "iron-borders_0.1.0_x64_en-US.msi", + "size": 9154560, + "checksums": { + "sha256": "4df0b207825e9ac5a60d8e3e2cd608aa76e665c9b550e9e0167f16a90d8441ba", + "sha512": "54e6b396e65bae859c396362f8c5f3c6cc8b18d6d338d29241565995871e8db6a1ed76268a8dc5f330077b1ae8986c5d872098bf1758de29d997d0070f130458", + "md5": "01cb21e9cab2b76918c47db7b7685024" + } + } + ] + }, + "windows-aarch64": { + "files": [ + { + "type": "exe", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/windows-aarch64/iron-borders_0.1.0_arm64-setup.exe", + "filename": "iron-borders_0.1.0_arm64-setup.exe", + "size": 5664249, + "checksums": { + "sha256": "4cab4d2568717c50c7fd6f397eba95db45259c09215ebff23bf4a99ceec175d0", + "sha512": "1bc6c53233c9934aeb87f911f53ed0e0090f9e3164abde7e72e6f119489420ffec48509d07ea29b147d29e791ea507cfc7422f1d6025ca054b21a7f26b4b9105", + "md5": "0878dd2c37501774a494d25d57c890a3" + } + }, + { + "type": "msi", + "url": "https://cdn.borders.xevion.dev/releases/v0.5.7/windows-aarch64/iron-borders_0.1.0_arm64_en-US.msi", + "filename": "iron-borders_0.1.0_arm64_en-US.msi", + "size": 8593408, + "checksums": { + "sha256": "2ef849bd77082afff7ea3d624c37ccc6ffa3ab53326f630c66323db7769af775", + "sha512": "54de9b1805a75d9134e7ce0b1697f86cadc88a2986168ae99b74ee4a365949991b2ee05f4072859c278646b0972c6d13d889164b3c99ea5b28f05dec9f308d02", + "md5": "d40f963278fb6488bfe4e264448438e5" + } + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/scripts/release-manager/package.json b/scripts/release-manager/package.json new file mode 100644 index 0000000..443010a --- /dev/null +++ b/scripts/release-manager/package.json @@ -0,0 +1,26 @@ +{ + "name": "release-manager", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "cli": "tsx cli.ts" + }, + "dependencies": { + "@actions/artifact": "^2.2.4", + "@aws-sdk/client-s3": "^3.908.0", + "@aws-sdk/lib-storage": "^3.908.0", + "@octokit/rest": "^21.0.2", + "adm-zip": "^0.5.16", + "commander": "^13.0.0", + "dotenv": "^16.4.7", + "glob": "^11.0.0", + "ora": "^8.1.1", + "picocolors": "^1.1.1" + }, + "devDependencies": { + "@types/node": "^22.15.8", + "tsx": "^4.19.2", + "typescript": "^5.9.3" + } +} diff --git a/scripts/release-manager/pnpm-workspace.yaml b/scripts/release-manager/pnpm-workspace.yaml new file mode 100644 index 0000000..efc037a --- /dev/null +++ b/scripts/release-manager/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/scripts/release-manager/tsconfig.json b/scripts/release-manager/tsconfig.json new file mode 100644 index 0000000..f437370 --- /dev/null +++ b/scripts/release-manager/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": ".", + "types": ["node"] + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/release-manager/types.ts b/scripts/release-manager/types.ts new file mode 100644 index 0000000..83797a4 --- /dev/null +++ b/scripts/release-manager/types.ts @@ -0,0 +1,54 @@ +export interface Checksums { + sha256: string; + sha512: string; + md5: string; +} + +export type FileType = 'appimage' | 'deb' | 'rpm' | 'dmg' | 'app' | 'exe' | 'msi'; + +export interface ReleaseFile { + type: FileType; + url: string; + filename: string; + size: number; + checksums: Checksums; +} + +export type Platform = + | 'linux-x86_64' + | 'linux-aarch64' + | 'macos-x86_64' + | 'macos-aarch64' + | 'windows-x86_64' + | 'windows-aarch64'; + +export type PlatformFiles = { + [K in Platform]?: { + files: ReleaseFile[]; + }; +}; + +export interface Version { + version: string; + released: string; + visible: boolean; + platforms: PlatformFiles; +} + +export type DisplayMode = 'latest_only' | 'all' | 'recent_n'; + +export interface Manifest { + latest: string; + display_mode: DisplayMode; + recent_n?: number; + versions: Version[]; +} + +export interface ArtifactMapping { + artifactName: string; + platform: Platform; + expectedFiles: { + glob: string; + type: FileType; + }[]; +} diff --git a/scripts/release-manager/utils.ts b/scripts/release-manager/utils.ts new file mode 100644 index 0000000..e54deae --- /dev/null +++ b/scripts/release-manager/utils.ts @@ -0,0 +1,162 @@ +import { S3Client } from '@aws-sdk/client-s3'; +import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; +import { existsSync } from 'fs'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; +import type { Manifest } from './types.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export function getRepoRoot(): string { + let currentDir = __dirname; + + while (currentDir !== dirname(currentDir)) { + if (existsSync(resolve(currentDir, '.git'))) { + return currentDir; + } + currentDir = dirname(currentDir); + } + + throw new Error('Could not find repository root (no .git directory found)'); +} + +export function getReleasesDir(): string { + return resolve(getRepoRoot(), 'releases'); +} + +export function createR2Client(): S3Client { + const accountId = process.env.R2_ACCOUNT_ID!; + const accessKeyId = process.env.R2_ACCESS_KEY_ID!; + const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY!; + + return new S3Client({ + region: 'auto', + endpoint: `https://${accountId}.r2.cloudflarestorage.com`, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); +} + +export async function loadManifest(bucket: string, s3Client: S3Client): Promise { + try { + const response = await s3Client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: 'manifest.json', + }) + ); + + const bodyString = await response.Body?.transformToString(); + if (!bodyString || bodyString.trim() === '') { + throw new Error('manifest.json is empty'); + } + + const manifest = JSON.parse(bodyString); + + if (!manifest.versions || !Array.isArray(manifest.versions)) { + throw new Error('Invalid manifest.json: missing or invalid "versions" array'); + } + + return manifest; + } catch (error: any) { + if (error.name === 'NoSuchKey' || error.Code === 'NoSuchKey') { + throw new Error('manifest.json not found in R2 bucket. Upload a release first.'); + } + + if (error instanceof SyntaxError) { + throw new Error( + `Invalid JSON in manifest.json from bucket "${bucket}".\n` + + `Please fix or delete the manifest and try again.` + ); + } + + throw error; + } +} + +export async function createManifest(): Promise { + return { + latest: '', + display_mode: 'latest_only', + versions: [], + }; +} + +export async function saveManifest( + manifest: Manifest, + bucket: string, + s3Client: S3Client +): Promise { + try { + const manifestJson = JSON.stringify(manifest, null, 2); + + await s3Client.send( + new PutObjectCommand({ + Bucket: bucket, + Key: 'manifest.json', + Body: manifestJson, + ContentType: 'application/json', + }) + ); + } catch (error: any) { + throw new Error( + `Failed to upload manifest to R2 bucket "${bucket}": ${error.message}\n` + + `Hint: Check that the bucket exists and your R2 credentials are correct` + ); + } +} + +function stripAnsi(str: string): number { + return str.replace(/\u001B\[\d+m/g, '').length; +} + +export function padWithColor(str: string, targetLength: number): string { + const visibleLength = stripAnsi(str); + const padding = Math.max(0, targetLength - visibleLength); + return str + ' '.repeat(padding); +} + +export function constrainString(str: string, maxLength: number): string { + if (str.length <= maxLength) { + return str; + } + + const ratio = maxLength / str.length; + + if (ratio < 0.65) { + const charsPerSide = Math.floor((maxLength - 1) / 2); + const start = str.slice(0, charsPerSide); + const end = str.slice(-charsPerSide); + return `${start}…${end}`; + } else { + return str.slice(0, maxLength - 1) + '…'; + } +} + +export function formatRelativeTime(date: Date): string { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffHours = diffMs / (1000 * 60 * 60); + + if (diffHours > 12) { + return date.toLocaleString(); + } + + const diffMins = Math.floor(diffMs / (1000 * 60)); + + if (diffMins < 60) { + return `${diffMins} min${diffMins !== 1 ? 's' : ''} ago`; + } + + const hours = Math.floor(diffMins / 60); + const mins = diffMins % 60; + + if (mins === 0) { + return `${hours} hour${hours !== 1 ? 's' : ''} ago`; + } + + return `${hours}h ${mins}m ago`; +}

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 JYRj{U{|ylm7fS#D literal 0 HcmV?d00001 diff --git a/frontend/src/assets/settings.png b/frontend/src/assets/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..db7f2f17c598bb6ce95616095e2d80393280ed02 GIT binary patch literal 2520 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 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/singleplayer.png b/frontend/src/assets/singleplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..0a5377f40ca2e5761b1f8c2c02f872f1411382c1 GIT binary patch literal 1519 zcmeHH+f&jB7^ShaEUT5>v|PND#%AWOW?~SUnFC&eLfcj}F|oX7NrFJ=Y^G@AWz9;< zkTA;{r9#ZaTQe-+nyX;m_$^|F7c>yE6bdI@n%RBZKVcuvIdjf@FW;P*Q*<1Iva#M_ z4FZ8|LeNKXW~G~9Sa122=RQ%*vf%d5pc%Tjo3S+#_u3?`0!f4lmg| zh~&-^OE2F-USP3JeA-rG-v3eHc+RRRrprI)0K>Iyab@A-fEocfYvjG7xp#)f6C4=0 z#NO5cD45^p`^j~D=c*FkN}tbbLO zk=zo##SXrftaZsl21ZMW7FgJ@%QL70(;L_`SmixT$-RxKAP9qsSc9xdP1acFAopY~ z_}0RRLS99hJBg9`0juqJ;nUi*++si9_?vc9fAUBHzdo+Vv|B@j9lhC_2Z-S3MoD6g zHj?{B{zDGEd^?n0_jo))yCC%eur+lf5uMQGGr%kl#a3}Ut1Zs%$7=zVZcodr;-)@F zo$WoVScFp|K-8aUdI5DxLCq)AAtZ-q3Ve4?`ig7l!9fGMx82sbwYBnbDb>?4fZztD zf0>X`wjC-rjcD$xe=;V#6s`<8LU4_gL-_Eh61%t`8|0HcU8LJmpxTnJj5EvzJJN|t zzBGgkGd#ks{9HQhx+G{d8O4~!v7#?ia7`?^Ftdp28NYe&sy~e0EeAxR{)OOC<}ath zvPxKjAv@&&Qc7~~!q22>}I+)#%{0-^d0=Fo+pi+$Es8hAE6ZKt|&yiwk*-gNEzTgm*qy@$^YeL?s3KIVr19PZQ z;6OnaXC&dx=eHl_tCc&43t4-c%bnaI2vpLpMDrj08kr621Q*hb(8C!*}UMOkYLQwCr9G0{0&h+;^_bY literal 0 HcmV?d00001 diff --git a/frontend/src/assets/singleplayer.psd b/frontend/src/assets/singleplayer.psd new file mode 100644 index 0000000000000000000000000000000000000000..3b5588800d282e00630329351aacafa6f688c16c GIT binary patch literal 29550 zcmeHP3wTu3wceA2BtXD~$MtoqF+zQO4VfemNaP_22&n|p5YXD*+IgHLBa@kNW&%OI zt+u`DXT7!}T54OMfMC6q`ry$EMIeB304s0bJVM~`3?w8udCtE7+H>aQB!`dhCQskHt1e@+VuRQip3?%EgVFQ<5F_aVhtVDliv#@}x4y)Q7y%%!j5I+a8)@ z%eJRX7~gMfWlm+er(E(|k}J!dE?-XNxD<=sZIyCRCw5v&vJmml8J9AVI+E`#C``_C zd!^*;5$UNmvpF+)boPkstZcJo%;@hVXP7farI|C+%vq`F**WPMIq26uDdYR0Hr8t| z$tjw2mo^_-<5J4}eoszX+Pr!5M$F3`;r5oMrDtbnr?oXUA~)x zNFs0so#fh;g1qJ%tz1mw|-s{vp z|Kz!Ff#%X)NBWBAd!)1?$>*-{+N8;Iv6N1~!fj(QdrqELviRNJVz=8lKDhCPp+zNU zWR1v5zV#l5%kG}%yKQV*cll2DcdC*mS^Uy?3XjxubEaO6GUfkzGt7mYFj$-KfJL*3S$!qQ@tHR!S1a!TCZa*Kbw#p7{0 zY!-^Ww7D+37E$`vj!o+nHH_E$bMo9yw-?4r<1@#m^~N@g65)`O@ACOAE}N7;X*?Js z91eR!?&~mNnZtYV?>AyJQVbWKh;) zq8VNX(8=O7$k1Bsn9OX;N;i+m%19kK+LoC*$~tmXYPK~qD>ZYBCA-9&Va?32Wre2b zMNN;XraF8G;Q0b|bTAO`GgEr7E0=TAqzkGNQdd^ubC>w%S-jH3 zQbfq-f97k4+1D`-|8Gw`;EC{om{ZcH4qKb&}UNzQpS;Pj-}BN~JW9t2Ei~ zPX64pJh)tgjxTYoKFi#HEZ)L{kN8=Q;5^+~J zUeaYE?$TWmP9@^5aJ;0;MBJsjBAiOZUEz31mx;JbcSSgr&vaM6&c_)g7oK69hldr# z(~ddf>Bo~e($kQ^Cnx)RybhOtMup!~;Rmx1i4^+$Gp%@bO-SUs{F3CVD5nmp6U}pa zseP3gJJV4~61fgPjiK(KPRTnRkA@UazPA{x;AFu8gAC=0rm-^@G zjmhQCNqPf*>{Oj=n^USeBIqSnG$l6=)`-Vgi|onCc6WvKPWLggP<*C%T|IcY&Yp6) zUVHH@m;bJti=4E#`X|?EPwtIhNjM@LI2SF(?53ceZ;3WN?Hp_m(Azf%+sIDFs9&_BFN=0xal|30<(A;n(EVrz z@i7VO8%2F8ODicsj}yn(V6Vpc<8NQoIT`3NvHNfg$&lS9M(3Kw5jvaEW^^v|UO&;N zsk_%M{*UwtUyy2?{+g#i4K1^1nKZAG)nqsMBT9^o7aR>WqV5D6Mbk8iY64N9GwRMR zeYC?t{|EQyI%-Bwz#-@rYKERvC%C-_sOM!)W(PO48$v`o7Cg8~EiRZA0^Rr!yNht&GPn1kiB3mp=x^dx!NpGvv9amWpSs4bDvp>d&2`cVGyc?sk!{`}*A;Tuk^RBg*!30*-rd6RZNhd+_^X+snF( zi*xc;ng~}52{b`3M} z5BLR^AfD8}Kd*pA@me-ew3z)-2$}fD>=!H{K0dy0d_v#82}%9?^-mglRYJm5L$4V; zcGI1n8XJL(MyT*B^htx^|jaRCK5_k(pvl(NQr` zed1!{`$Q*XqBAKv=B7dEeI|a-GC1YI$1-AvJp0Gjb8j9tv}mg}bENlY`|{$3FPM4h zc%98R>R*<<@$+v_dal?$xoWvT>+1cps-^ljf3fXA#fi&z-Li1Sf2?_a`@x!qm)7n$ zbh2@F$=t_ZSh?=)!>5`?Pr0Xb-V?uBwf>#=6*Y-PMZww_VN+aepHaeuo6-lxz=8); z2KUK$?Aak?!PcUEmokT0k9&W%AaCYSn{Q;@@K`b+?%SiP_QQzfSKBAg%JNIqx(Qdd z;FeAk_(v=uTFjZmCa}ih1^L5o8@3?-yZOTw6fMXfwr~X)6ZqaMb?m!R$NnpI9B3?F zcyH6+?w@^Y*;2B;vm$!q zBZ-s#bK>+@X3blfwf$$(^%sUtl5Z$n^~}^~|Mb%1!*8EzZT;oXCob?`H)PVWi64CQ z(M`~JJE>j54>e#>#U)~nCBC*X&ulCDbd-IWps~`F6^k?ok64-w2TuZ_DMb9<-swP+V z|82B7<>c)AM`t|#%Bq!ZKm5_*Z9klU+kv4;*DSl^L+f`chi^ISUoz~MpKg3GW8&iP z{ott+cRXEr*DKTBd1CPK9X|Ww+>++Ro44kyIycC^pl{;c zGoE;M`o}*^$n^dGrExaQ*%s9$H|K(Ph9!v2fimZZu@o8bIW)9 zXvKFo+%{~%^jvJwLeBW!hGKXA(?9L?a~{ErtCc)Gj-g;yrKVg z_Mx=Q2hvw>c=sm{ek^}~^~%E|uT#hEdhWi%PnJDa^M@SstJ7C)NcfA4-FfIxC32S=b9erU0YxIl0-D=rn20bymR`%!$N=7GeJ&yh%G8O)3lyhsyhnnH7! zG71XV?Wn}FIMX2UlY_2yndv(5Q;;UNny%OD514M%WN<-YN*9+EH$uj1$`EzJ-e;e1 zh|38Y`!dQw7QOe^jNzJ7qo4go6h^N($)OjVWG9DSQ35Rhw56ut+;mO6G)kZ2;ib`f zU0jwxHr5oa*@>&J&b63QI@e;l-ME%0Q%Hv?BdSxkm>7qtD6}4pUUdwAh8Bry^LgOX z=RC?B7DK$4zY-%Vg+|~=*N7?Pal2z~GKZs~YAi|Mb8GMlpGJLliJNAbFL{&Aq{ms| za$qd^I*v`lFNpaF3olE?28-yytBAbli*Z`!dxiXb2VEfPe-Uj?D_s)C(1NJn;VP-r z`bB$=YmVF4cKSReUD`G$>8FK=59#ogmD3zkQN(WvTZJ$5lXE0^iNr_2D0(pxy+}sm z^o5m@x7blG(XZTzALR_Ly`iH+*ilmHrO-~?0x!|$&Pk6FAx$x#`Hkz+8!uXk;ur_3 zuQWDZ^wH7yO@yDRgm?+K{zza0SwDPkKaQEOEO(H>;j1@BRLY_t^Ku@ALk_Mv{ zow`FcopQbik+Fz%@E2Jd|2=Eri&-;Y!W#K9cA2kW_53Avk-sd;dH!GQ3|}pZ!e3(w zH!=POJBd=m-()BFnr<`-s@Jnm`3Ck8-^4yd1mcGn3-At1wU6!K`?Uf!2U!(wN05HZ z*7IYctmDU-v8>@In6a$o)r`tIq4p%kFxJjbvDZ;Hpx(x7*hbJ6ev)m5ZlYUxD{BS6 zO3)K*8*f4T1bdsSpw&9v$(zvbfVqvL1ly#)1#O}#lhM~I>a9%W&1lmYnwOTyz*E_7 z-T;?0u|51UtA@-Tw2$yM_Ac5-VZ(d89&K_;ryA@X2+Y z9|t#pl^o{P@DsIEUc-;FW9V%`>l{Wf{t0>l*vDGf%TVLAQ#IH)4%4Zl9>&%2v+OK5 z9av~HhBjb9b(r`P3=P0x0nx`03JthIMKqj4H+8i_XA5S#AlAxYUn4|0)*0Y+;O&M@ zpJ1Y0sDA+UGV1SxHek#r7<&kEO{|Ik1-v%sd=2e3O#2#iv?J2~!rE|L%{o{u_Js4l zvw)}tP&*G!JAaQ|z>ExjXb0~McD!9!upe~=v+u{kPGV6+$=X`MI|J<;3+I?kMF5_L zD>^W)8SQ4iSG4OzWW2{tp|25zcCwAXiy$XYQnc*CZjc`G{4VV1ZtUhRwwJ%d_VKsb z0lu9boH=1o<0QP7(B1$Th<&L|Z`D8)z%q z>tIg{$~wrZC~IJI1MGPdr5-lELAVw5HTcHjPR8ecUr@jL-zv49*Qj6S@f#H($_rol zZh=YP7iU zUO4fRL{|y@xWJDh#_o36lQpd|%z=M_*oe!L@cKme{dR}XgWm@C&vi)iy4*^>nmAY1 z8mA5HVr~qLF>=Um+&#|6bj2v0cK64!izQoGey5!S9llvU{Qn8^)ML17orHU|k{AA= zS5Z2r;hl-v&2RDh<*owX9O%aVN4gpjpA4GUh96wG7+Emue0!|X zuPhcXa`d@fU3hG=OTRlU>d@C8uaEW6t8k|j&O$SZLeDAXbuzVE`LV$ch{ToP6_FKQ zy30JYt8{T0CbftDAKB}OL5AsOR(L#ayyY$XtQO>Ks<7d>x zqA2Q8uBaxiF5@RrYSiWYgu0>|jpC}hihruU!aq`9U)E>eZ#@p4y{B@KKsJE#H`9{zd^$_0- z-9)#lt-KZdDnSqMZE6eJ2l(5n3c6pXJJlw%anDf29Z&Tn54K5v3)(~#E~Bqi)LXI4 z(`eHenwOTyz*G2cwE-?U#rLR}c{ODApnXI=$KOT!sCt&ahdV<@g#ERAzj_fo8e7W` zL*^)SbfEr$dPeZcbzH6E$H5Q4ro*ULtH(rJMZHEn%8x;&MZLt&skoo1euBOLcC(Jx z!fu8-ryZ-o2I2;+)>V(C*TIyt;B{ch%^2Q*rPaZNOKLNw!kuK%cL_n!z)dRR;vBlE z3-`fcU5kploe-gVBPfR_0_t(_cEbyMFx4*9cS604`a7Tv81o6_cR;R*H>pd&YlF_E zXt!b7rO?rim|Mcza9qqgu!3sv-r@mK3!ru$oOX3HzaVBliTYbu*eOu5pbB*bvsYnZ zC$T7^WNoeBoq={#P})GY^3!le2b#@jH>+DjyI#b|W_}8NjVQFIZR#e3JNc60W&`$x z^w3^!z}{}ezHZ=q)%ARzx{e=E*YJbto6yyPv*-=j(*e)D20L(vTwM*j+dy9h#eW%8 zI#vFRl~XLe47p}_g=h=tDg$jrdnN2?L0Jh|+#|+m*9?!g;uLH}JhkC;{3an-?`!1> za1EtH0j?>)H3hh)0M``YngU!?fNKhH4duK7TvLE+D2f7HQ%qa|uA$T@z%>Q9)`dnv z1h}RE*A(EIf`CH_D8MxZxTXNtM1dOMngU!?fNKhH4P~7ITr-q43UJL()+)d?lyyQa za7_W;D8Mz84XC#%z%|eo1-J&?M7JuyHSnth1+FQGPw0GG0j@z;i0)K?Q81G$z%>Q9 zCfcMQxF+TSuA#40)Depca7~kG6H7#_E5J1cxTXNt6yO?U_Mm-40j{BaQ~|Exe&!Kj zKX6R}u8Fb0HOL%=?hXaGrU2K347m@u27UlG9Y(!c0j{B~qF$o_*C5xT0M``YngUz{ zZAM=$>}IHQ+A-i7#M-s4dMv#Trkn+@150kk@CGcc4klbu0A6riK=c9EV7^H~TmaWZ z7mxzhwJ5+fh)}%|l*1DN1-ORYR2ATw0$fvoYYK2p0j@z!3#?-ba1F9e3UEyUt|6uZ zpqD^7ro9M0a1GH0TvLE+ioiA00;rt_ZC8M6VrJkPcxT}2c3}Z<4gOaVUT5K}W@pdEjfQGja*#nW&`2gWs{-K+rD(5@FT0$fAA5hVaWw<*9i;Y*4e;F{1w{s*op zz%|gl3UEyUt|`DZ1-J&M)+@j@(8~&N4YWZ4u3@eQoE8_LLsjTh0j^q4WrECbhM;F=6vlM(VL z0U5X^1J`8WnkY~MT$6!oGH^`>uA!`xfoq1cMh31K%32w?hO$nm1+K}!9T~WWvH|rr z8Mp@8A_Lc;o9I>B21J}enz%}%> ziaKIZ2Ciu`ZDNUtbs4xO1J`8Wnhacn%pSCl$iOwUkIKL`+=V|P><6yNz%?-zxCWV{ z(A^;e*JR+DkRkU0*T4_Jro*UL%fL0XRn%)_;2PvwWZ;?%T$6!opv~y3h20EwPCEu% zgIK%PRgb0D!IZP$bzsTO7~X)T)xm^IGH?y93y4188q7DzhzsDF=mJu}x)vF@1`(<^ zf^v8wAOqL1o2m?4lYwh8a7_lT$-p(JX@PZ22ChN2Nd~UTz%|5F0Q3?l$Fvv02d*L7 zfNL^vO%}L@S^%~4pzShnP0S2j1Mduc-7YKucESHD!s{%2)hsAk3tU6}47B3{8wI$A zP&^H1bYNUF+RZX>4efdnBfvG(8&LxAbDIoY6TYOl0j>!>KWWy~3*z%?1T zhIS*UiZ!S>?V91yR-AgRh^IE3j=drnd;A;p<@`zfcjHOqX*`7-ho_N2@(l7GeuX@U z1;~$Bh#ZUOkt?!Dl=J*|$hTN5io%x$^OKe#bz>QlaF!!KX+<|m=@+YzpY#gylV0tX zpR^UZNmW`w^2~NEKWR7ejP{DM4jClIvW6c(eo|*yi|i9B!Th8{T7J@Dx#eiG$EQJ&HPEkEgimY1|&r(N@tPO_f)NlLf;q|?3RCn;Kf(kU%J>9m%g zbWY1pI%~*Jsx{;%)f)1XYP;np)rp*>V1`m1a-KptN_B93C`;*5w=AVPWcOSlPpJ+Y z+&xq2(iJk5E+Hs-$y7RXg-oU6h$qTa+JoF7%2e8k7^6(3cR)j#N;|Ynr6u4|rqWU^ zQ)wvzhccCxh)ku$dZyA_dZyA_TBg!wL#EPOh|Ex?5@iNarc#xbsYJ9D|Hz|ECF(O~ zDsAnSskFtAskFIUrqU)urqTvOrqTxN?MCeD24pI&N2by`WGbyerqY{QrqUajlQNZF z(=wG-Yne)~8ZwpstYs>_3^~eFB1)M`s|=Jfl~!t*N-MDz%2ZmRWhyP#GL@F~ieLW& D&wGeL literal 0 HcmV?d00001 diff --git a/frontend/src/browser/analytics/index.ts b/frontend/src/browser/analytics/index.ts new file mode 100644 index 0000000..fe73c09 --- /dev/null +++ b/frontend/src/browser/analytics/index.ts @@ -0,0 +1,5 @@ +import { WasmAnalytics } from "./wasmAnalytics"; +import { wasmBridge } from "@/browser/api/wasmBridge"; + +// Create singleton instance using the worker from wasmBridge +export const wasmAnalytics = new WasmAnalytics(wasmBridge.getWorker()); diff --git a/frontend/src/browser/analytics/wasmAnalytics.ts b/frontend/src/browser/analytics/wasmAnalytics.ts new file mode 100644 index 0000000..8fc29ce --- /dev/null +++ b/frontend/src/browser/analytics/wasmAnalytics.ts @@ -0,0 +1,30 @@ +import type { AnalyticsAPI } from "@/shared/analytics/AnalyticsAPI"; + +/** + * Browser/WASM implementation of AnalyticsAPI. + * + * Events are sent through the WASM worker which handles batching and + * sending to PostHog. + */ +export class WasmAnalytics implements AnalyticsAPI { + private worker: Worker | null = null; + + constructor(worker: Worker) { + this.worker = worker; + } + + track(event: string, properties?: Record): void { + if (!this.worker) { + console.error("Worker not set for analytics"); + return; + } + + this.worker.postMessage({ + type: "ANALYTICS_EVENT", + payload: { + event, + properties: properties || {}, + }, + }); + } +} diff --git a/frontend/src/browser/api/wasmBridge.ts b/frontend/src/browser/api/wasmBridge.ts new file mode 100644 index 0000000..eeca0f4 --- /dev/null +++ b/frontend/src/browser/api/wasmBridge.ts @@ -0,0 +1,344 @@ +import type { GameAPI } from "@/shared/api/GameAPI"; +import type { AttacksUpdatePayload, GameOutcome, LeaderboardSnapshot, ShipsUpdatePayload, SpawnPhaseUpdate, UnsubscribeFn } from "@/shared/api/types"; +import { decodeTerritorySnapshot } from "@/shared/utils/binaryDecoding"; + +/** + * Browser implementation of GameAPI using WASM bindings in a Web Worker. + * + * This wraps the WASM-exported functions from borders-wasm to match + * the unified GameAPI interface using the new UI event system. + * The WASM module runs in a dedicated worker to avoid blocking the main thread. + */ +export class WasmBridge implements GameAPI { + private worker: Worker; + private initialized: boolean = false; + private messageQueue: Array = []; // Queue for messages sent before worker is ready + private renderer: any = null; // GameRenderer instance + private snapshotCallbacks: Array<(data: LeaderboardSnapshot) => void> = []; + private gameEndCallbacks: Array<(outcome: GameOutcome) => void> = []; + private attacksUpdateCallbacks: Array<(data: AttacksUpdatePayload) => void> = []; + private shipsUpdateCallbacks: Array<(data: ShipsUpdatePayload) => void> = []; + private spawnPhaseUpdateCallbacks: Array<(update: SpawnPhaseUpdate) => void> = []; + private spawnPhaseEndedCallbacks: Array<() => void> = []; + private queuedRenderMessages: Array = []; // Queue for messages received before renderer is ready + + constructor() { + this.worker = new Worker(new URL("../workers/game.worker.ts", import.meta.url), { + type: "module", + }); + + this.worker.addEventListener("message", (e) => { + const { type, payload } = e.data; + + if (type === "INIT_COMPLETE") { + this.initialized = true; + console.log("WASM worker initialized, processing queued messages"); + + // Process any queued messages + while (this.messageQueue.length > 0) { + const queuedMessage = this.messageQueue.shift(); + this.worker.postMessage(queuedMessage); + } + return; + } + + if (type === "BACKEND_MESSAGE") { + this.handleBackendMessage(payload); + return; + } + + if (type === "ERROR") { + console.error("Worker error:", payload); + return; + } + }); + + // Initialize the worker + this.worker.postMessage({ type: "INIT" }); + } + + private handleBackendMessage(message: any) { + // Messages use msg_type tag from serde + switch (message.msg_type) { + case "LeaderboardSnapshot": + this.snapshotCallbacks.forEach((callback) => callback(message as LeaderboardSnapshot)); + break; + + case "AttacksUpdate": + this.attacksUpdateCallbacks.forEach((callback) => callback(message as AttacksUpdatePayload)); + break; + + case "ShipsUpdate": + this.shipsUpdateCallbacks.forEach((callback) => callback(message as ShipsUpdatePayload)); + // Also forward to renderer for visual updates + if (this.renderer) { + this.renderer.updateShips(message as ShipsUpdatePayload); + } + break; + + case "GameEnded": + this.gameEndCallbacks.forEach((callback) => callback(message.outcome as GameOutcome)); + break; + + case "SpawnPhaseUpdate": + const update: SpawnPhaseUpdate = { + countdown: message.countdown + ? { + startedAtMs: message.countdown.started_at_ms, + durationSecs: message.countdown.duration_secs, + } + : null, + }; + this.spawnPhaseUpdateCallbacks.forEach((callback) => callback(update)); + break; + + case "SpawnPhaseEnded": + this.spawnPhaseEndedCallbacks.forEach((callback) => callback()); + break; + + case "HighlightNation": + this.renderer?.setHighlightedNation(message.nation_id ?? null); + break; + + default: + // All other messages are render messages + this.handleRenderMessage(message); + break; + } + } + + private sendWorkerMessage(message: any): void { + if (this.initialized) { + this.worker.postMessage(message); + } else { + this.messageQueue.push(message); + } + } + + startGame(): void { + this.sendWorkerMessage({ + type: "FRONTEND_MESSAGE", + payload: { msg_type: "StartGame" }, + }); + } + + quitGame(): void { + this.sendWorkerMessage({ + type: "FRONTEND_MESSAGE", + payload: { msg_type: "QuitGame" }, + }); + } + + onLeaderboardSnapshot(callback: (data: LeaderboardSnapshot) => void): UnsubscribeFn { + // Add callback to the list + this.snapshotCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.snapshotCallbacks.indexOf(callback); + if (index !== -1) { + this.snapshotCallbacks.splice(index, 1); + } + }; + } + + onGameEnded(callback: (outcome: GameOutcome) => void): UnsubscribeFn { + // Add callback to the list + this.gameEndCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.gameEndCallbacks.indexOf(callback); + if (index !== -1) { + this.gameEndCallbacks.splice(index, 1); + } + }; + } + + onAttacksUpdate(callback: (data: AttacksUpdatePayload) => void): UnsubscribeFn { + // Add callback to the list + this.attacksUpdateCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.attacksUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.attacksUpdateCallbacks.splice(index, 1); + } + }; + } + + onShipsUpdate(callback: (data: ShipsUpdatePayload) => void): UnsubscribeFn { + // Add callback to the list + this.shipsUpdateCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.shipsUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.shipsUpdateCallbacks.splice(index, 1); + } + }; + } + + onSpawnPhaseUpdate(callback: (update: SpawnPhaseUpdate) => void): UnsubscribeFn { + // Add callback to the list + this.spawnPhaseUpdateCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.spawnPhaseUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.spawnPhaseUpdateCallbacks.splice(index, 1); + } + }; + } + + onSpawnPhaseEnded(callback: () => void): UnsubscribeFn { + // Add callback to the list + this.spawnPhaseEndedCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.spawnPhaseEndedCallbacks.indexOf(callback); + if (index !== -1) { + this.spawnPhaseEndedCallbacks.splice(index, 1); + } + }; + } + + // Rendering integration methods + private handleRenderMessage(message: any) { + if (!this.renderer) { + this.queuedRenderMessages.push(message); + return; + } + + // Handle flattened serde format: {msg_type: 'RenderInit', ...data} + if (message.msg_type === "RenderInit") { + // Apply all initialization data atomically + this.renderer.setTerrainPalette(message.terrain_palette); + this.renderer.initTerrain(message.terrain); + this.renderer.initPalette(message.palette); + + // Decode sparse binary territory snapshot + const sparseTiles = decodeTerritorySnapshot(message.initial_territories.data); + if (sparseTiles) { + this.renderer.applyTerritorySnapshot(message.initial_territories.turn, sparseTiles); + } else { + console.error("Failed to decode territory snapshot"); + } + } else if (message.msg_type === "TerritoryDelta") { + this.renderer.updateTerritoryDelta(message.turn, message.changes); + } else if (message.msg_type === "CameraCommand") { + // Handle camera commands from backend + if (message.type === "CenterOnTile") { + this.renderer.centerOnTile(message.tile_index, message.animate); + } else if (message.type === "SetZoom") { + this.renderer.setZoom(message.zoom, message.animate); + } else if (message.type === "PanBy") { + this.renderer.panBy(message.dx, message.dy, message.animate); + } + } + } + + setRenderer(renderer: any): void { + this.renderer = renderer; + + // Replay any queued messages that arrived before renderer was ready + if (this.queuedRenderMessages.length > 0) { + for (const message of this.queuedRenderMessages) { + this.handleRenderMessage(message); + } + this.queuedRenderMessages = []; // Clear queue after replay + } + } + + sendCameraUpdate(state: { x: number; y: number; zoom: number }): void { + try { + this.sendWorkerMessage({ + type: "CAMERA_UPDATE", + payload: state, + }); + } catch (e) { + console.error("Failed to send camera update:", e); + } + } + + sendMapClick(data: { tile_index: number | null; world_x: number; world_y: number; button: number }): void { + try { + this.sendWorkerMessage({ + type: "RENDER_INPUT", + payload: { + type: "MapClick", + tile_index: data.tile_index, + world_x: data.world_x, + world_y: data.world_y, + button: data.button, + }, + }); + } catch (e) { + console.error("Failed to send map click:", e); + } + } + + sendMapHover(data: { tile_index: number | null; world_x: number; world_y: number }): void { + try { + this.sendWorkerMessage({ + type: "RENDER_INPUT", + payload: { + type: "MapHover", + tile_index: data.tile_index, + world_x: data.world_x, + world_y: data.world_y, + }, + }); + } catch (e) { + console.error("Failed to send map hover:", e); + } + } + + sendKeyPress(data: { key: string; pressed: boolean }): void { + try { + this.sendWorkerMessage({ + type: "RENDER_INPUT", + payload: { + type: "KeyPress", + key: data.key, + pressed: data.pressed, + }, + }); + } catch (e) { + console.error("Failed to send key press:", e); + } + } + + sendAttackRatio(ratio: number): void { + try { + this.sendWorkerMessage({ + type: "FRONTEND_MESSAGE", + payload: { + msg_type: "SetAttackRatio", + ratio: ratio, + }, + }); + } catch (e) { + console.error("Failed to send attack ratio:", e); + } + } + + async getGameState(): Promise { + // WASM doesn't persist state across reloads - always start fresh + return null; + } + + /** + * Get the worker instance (for analytics or other purposes). + */ + getWorker(): Worker { + return this.worker; + } +} + +// Export singleton instance +export const wasmBridge = new WasmBridge(); diff --git a/frontend/src/browser/workers/game.worker.ts b/frontend/src/browser/workers/game.worker.ts new file mode 100644 index 0000000..9dbbfe5 --- /dev/null +++ b/frontend/src/browser/workers/game.worker.ts @@ -0,0 +1,84 @@ +import init, { + register_backend_message_callback, + send_frontend_message, + handle_camera_update, + handle_render_input, + track_analytics_event, +} from "../../../pkg/borders"; + +let wasmInitialized = false; + +type WorkerMessage = + | { type: "INIT" } + | { type: "FRONTEND_MESSAGE"; payload: any } + | { type: "CAMERA_UPDATE"; payload: any } + | { type: "RENDER_INPUT"; payload: any } + | { type: "ANALYTICS_EVENT"; payload: any }; + +self.addEventListener("message", async (e: MessageEvent) => { + const message = e.data; + + try { + switch (message.type) { + case "INIT": { + await init(); + wasmInitialized = true; + + // Register callback that posts backend messages back to main thread + register_backend_message_callback((backendMessage: any) => { + self.postMessage({ type: "BACKEND_MESSAGE", payload: backendMessage }); + }); + + self.postMessage({ type: "INIT_COMPLETE" }); + console.log("WASM initialized successfully in worker"); + break; + } + + case "FRONTEND_MESSAGE": { + if (!wasmInitialized) { + console.error("WASM module not initialized"); + return; + } + send_frontend_message(message.payload); + break; + } + + case "CAMERA_UPDATE": { + if (!wasmInitialized) { + console.error("WASM module not initialized"); + return; + } + handle_camera_update(message.payload); + break; + } + + case "RENDER_INPUT": { + if (!wasmInitialized) { + console.error("WASM module not initialized"); + return; + } + handle_render_input(message.payload); + break; + } + + case "ANALYTICS_EVENT": { + if (!wasmInitialized) { + console.error("WASM module not initialized"); + return; + } + track_analytics_event(message.payload); + break; + } + + default: { + console.warn("Unknown worker message type:", (message as any).type); + } + } + } catch (error) { + console.error("Worker error:", error); + self.postMessage({ + type: "ERROR", + payload: { message: error instanceof Error ? error.message : String(error) }, + }); + } +}); diff --git a/frontend/src/desktop/analytics/tauriAnalytics.ts b/frontend/src/desktop/analytics/tauriAnalytics.ts new file mode 100644 index 0000000..ff4eb99 --- /dev/null +++ b/frontend/src/desktop/analytics/tauriAnalytics.ts @@ -0,0 +1,32 @@ +import { invoke } from "@tauri-apps/api/core"; +import type { AnalyticsAPI } from "@/shared/analytics/AnalyticsAPI"; + +/** + * Desktop implementation of AnalyticsAPI using Tauri commands. + * + * Events are sent to the Rust backend which handles batching and + * sending to PostHog. + */ +export class TauriAnalytics implements AnalyticsAPI { + track(event: string, properties?: Record): void { + invoke("track_analytics_event", { + payload: { + event, + properties: properties || {}, + }, + }).catch((err) => { + console.error("Failed to track analytics event:", err); + }); + } + + async flush(): Promise { + try { + await invoke("flush_analytics"); + } catch (err) { + console.error("Failed to flush analytics:", err); + } + } +} + +// Export singleton instance +export const tauriAnalytics = new TauriAnalytics(); diff --git a/frontend/src/desktop/api/tauriAPI.ts b/frontend/src/desktop/api/tauriAPI.ts new file mode 100644 index 0000000..57b8458 --- /dev/null +++ b/frontend/src/desktop/api/tauriAPI.ts @@ -0,0 +1,421 @@ +import { listen } from "@tauri-apps/api/event"; +import { invoke } from "@tauri-apps/api/core"; +import type { GameAPI } from "@/shared/api/GameAPI"; +import type { AttacksUpdatePayload, GameOutcome, LeaderboardSnapshot, ShipsUpdatePayload, SpawnPhaseUpdate, UnsubscribeFn } from "@/shared/api/types"; +import { GameRenderer } from "@/shared/render/GameRenderer"; +import { decodeTerritorySnapshot } from "@/shared/utils/binaryDecoding"; + +/** + * Desktop implementation of GameAPI using Tauri's unified UI event system. + * + * This listens to the "ui:event" channel which carries all UiEvent enum variants + * from the game core. + */ +export class TauriAPI implements GameAPI { + private snapshotCallbacks: Array<(data: LeaderboardSnapshot) => void> = []; + private gameEndCallbacks: Array<(outcome: GameOutcome) => void> = []; + private attacksUpdateCallbacks: Array<(data: AttacksUpdatePayload) => void> = []; + private shipsUpdateCallbacks: Array<(data: ShipsUpdatePayload) => void> = []; + private spawnPhaseUpdateCallbacks: Array<(update: SpawnPhaseUpdate) => void> = []; + private spawnPhaseEndCallbacks: Array<() => void> = []; + private renderer: GameRenderer | null = null; + private messageBuffer: any[] = []; + private initReceived: boolean = false; + private pendingDeltas: any[] = []; + + // Store unsubscribe functions for event listeners + private uiEventUnsubscribe?: () => void; + private pixelStreamUnsubscribe?: () => void; + + constructor() { + // Start listening to UI events immediately + this.setupEventListener(); + this.setupRenderListeners(); + } + + private async setupEventListener() { + this.uiEventUnsubscribe = await listen("backend:message", (event) => { + this.handleBackendMessage(event.payload); + }); + } + + private handleBackendMessage(message: any) { + // Messages use msg_type tag from serde + switch (message.msg_type) { + case "LeaderboardSnapshot": + this.snapshotCallbacks.forEach((callback) => callback(message as LeaderboardSnapshot)); + break; + + case "AttacksUpdate": + this.attacksUpdateCallbacks.forEach((callback) => callback(message as AttacksUpdatePayload)); + break; + + case "ShipsUpdate": + this.shipsUpdateCallbacks.forEach((callback) => callback(message as ShipsUpdatePayload)); + // Also forward to renderer for visual updates + if (this.renderer) { + this.renderer.updateShips(message as ShipsUpdatePayload); + } + break; + + case "GameEnded": + console.log("Game ended:", message.outcome); + this.gameEndCallbacks.forEach((callback) => callback(message.outcome as GameOutcome)); + break; + + case "SpawnPhaseUpdate": + const update: SpawnPhaseUpdate = { + countdown: message.countdown + ? { + startedAtMs: message.countdown.started_at_ms, + durationSecs: message.countdown.duration_secs, + } + : null, + }; + this.spawnPhaseUpdateCallbacks.forEach((callback) => callback(update)); + break; + + case "SpawnPhaseEnded": + this.spawnPhaseEndCallbacks.forEach((callback) => callback()); + break; + + case "HighlightNation": + this.renderer?.setHighlightedNation(message.nation_id ?? null); + break; + + default: + // All other messages are render messages + this.handleRenderMessage(message); + break; + } + } + + startGame(): void { + import("@tauri-apps/api/core").then(({ invoke }) => { + invoke("send_frontend_message", { message: { msg_type: "StartGame" } }); + }); + } + + quitGame(): void { + import("@tauri-apps/api/core").then(({ invoke }) => { + invoke("send_frontend_message", { message: { msg_type: "QuitGame" } }); + }); + } + + onLeaderboardSnapshot(callback: (data: LeaderboardSnapshot) => void): UnsubscribeFn { + // Add callback to the list + this.snapshotCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.snapshotCallbacks.indexOf(callback); + if (index !== -1) { + this.snapshotCallbacks.splice(index, 1); + } + }; + } + + onGameEnded(callback: (outcome: GameOutcome) => void): UnsubscribeFn { + // Add callback to the list + this.gameEndCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.gameEndCallbacks.indexOf(callback); + if (index !== -1) { + this.gameEndCallbacks.splice(index, 1); + } + }; + } + + onAttacksUpdate(callback: (data: AttacksUpdatePayload) => void): UnsubscribeFn { + // Add callback to the list + this.attacksUpdateCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.attacksUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.attacksUpdateCallbacks.splice(index, 1); + } + }; + } + + onShipsUpdate(callback: (data: ShipsUpdatePayload) => void): UnsubscribeFn { + // Add callback to the list + this.shipsUpdateCallbacks.push(callback); + + // Return cleanup function that removes this specific callback + return () => { + const index = this.shipsUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.shipsUpdateCallbacks.splice(index, 1); + } + }; + } + + onSpawnPhaseUpdate(callback: (update: SpawnPhaseUpdate) => void): UnsubscribeFn { + this.spawnPhaseUpdateCallbacks.push(callback); + return () => { + const index = this.spawnPhaseUpdateCallbacks.indexOf(callback); + if (index !== -1) { + this.spawnPhaseUpdateCallbacks.splice(index, 1); + } + }; + } + + onSpawnPhaseEnded(callback: () => void): UnsubscribeFn { + this.spawnPhaseEndCallbacks.push(callback); + return () => { + const index = this.spawnPhaseEndCallbacks.indexOf(callback); + if (index !== -1) { + this.spawnPhaseEndCallbacks.splice(index, 1); + } + }; + } + + // Rendering integration methods + private async setupRenderListeners() { + // Listen for binary pixel stream (territory deltas) + // Note: Regular render messages now come through backend:message + this.pixelStreamUnsubscribe = await listen("render:pixel_stream", (event) => { + if (this.renderer) { + const uint8Array = new Uint8Array(event.payload); + this.renderer.updateBinaryDelta(uint8Array); + } else { + console.warn("Received pixel stream but renderer not set"); + } + }); + } + + private async handleRenderMessage(message: any): Promise { + if (!this.renderer) { + console.warn("Renderer not set yet, buffering message:", message.msg_type); + this.messageBuffer.push(message); + return; + } + + await this.processRenderMessage(message); + } + + private async processRenderMessage(message: any): Promise { + // The message structure from Rust is flat with msg_type field + + // Defensive check: ensure renderer is still valid + if (!this.renderer) { + console.warn("Cannot process render message - renderer not available, buffering:", message.msg_type); + // Re-buffer the message if renderer is not available + this.messageBuffer.push(message); + return; + } + + // Handle atomic RenderInit message first + if (message.msg_type === "RenderInit") { + const rendererAtStart = this.renderer; + + try { + // Process terrain palette, terrain, player palette, and initial territories atomically + rendererAtStart.setTerrainPalette(message.terrain_palette); + + // Check if renderer changed (keep check for safety) + if (this.renderer !== rendererAtStart) { + console.warn("Renderer changed during RenderInit processing - re-buffering message"); + this.messageBuffer.push(message); + this.initReceived = false; + return; + } + + this.renderer.initTerrain(message.terrain); + + // Check if renderer changed (keep check for safety) + if (this.renderer !== rendererAtStart) { + console.warn("Renderer changed during RenderInit processing - re-buffering message"); + this.messageBuffer.push(message); + this.initReceived = false; + return; + } + + this.renderer.initPalette(message.palette); + + // Decode sparse binary territory snapshot + const sparseTiles = decodeTerritorySnapshot(message.initial_territories.data); + if (sparseTiles) { + this.renderer.applyTerritorySnapshot(message.initial_territories.turn, sparseTiles); + } else { + console.error("Failed to decode territory snapshot"); + } + + this.initReceived = true; + } catch (error) { + console.error("FATAL: Failed to process RenderInit:", error); + throw error; // Re-throw so it's visible + } + + // Process any deltas that arrived before initialization + if (this.pendingDeltas.length > 0) { + for (const pendingDelta of this.pendingDeltas) { + await this.processRenderMessage(pendingDelta); + } + this.pendingDeltas = []; + } + + return; + } + + // Buffer deltas if initialization not received yet + if (!this.initReceived && message.msg_type === "TerritoryDelta") { + this.pendingDeltas.push(message); + return; + } + + // Process other messages normally + switch (message.msg_type) { + case "TerrainInit": + console.warn("Received legacy TerrainInit - should use RenderInit instead"); + this.renderer!.initTerrain(message); + break; + case "PaletteInit": + console.warn("Received legacy PaletteInit - should use RenderInit instead"); + this.renderer!.initPalette(message); + break; + case "TerritoryDelta": + this.renderer!.updateTerritoryDelta(message.turn, message.changes); + break; + default: + console.warn("Unknown render message type:", message.msg_type); + } + } + + async setRenderer(renderer: GameRenderer | null): Promise { + if (renderer === null) { + console.log("Renderer unregistered from TauriAPI - cleaning up"); + this.renderer = null; + this.initReceived = false; + this.pendingDeltas = []; + this.messageBuffer = []; + return; + } + + this.renderer = renderer; + console.log("Renderer registered with TauriAPI"); + + // Process buffered messages asynchronously to avoid blocking UI during HMR + if (this.messageBuffer.length > 0) { + const bufferedMessages = [...this.messageBuffer]; + this.messageBuffer = []; + + // Process messages in next tick to avoid blocking + setTimeout(async () => { + for (const message of bufferedMessages) { + await this.processRenderMessage(message); + } + }, 0); + } + } + + destroy() { + // Unsubscribe from all Tauri event listeners + if (this.uiEventUnsubscribe) { + this.uiEventUnsubscribe(); + this.uiEventUnsubscribe = undefined; + } + if (this.pixelStreamUnsubscribe) { + this.pixelStreamUnsubscribe(); + this.pixelStreamUnsubscribe = undefined; + } + + // Clear all callbacks + this.snapshotCallbacks = []; + this.gameEndCallbacks = []; + this.attacksUpdateCallbacks = []; + this.shipsUpdateCallbacks = []; + this.spawnPhaseUpdateCallbacks = []; + this.spawnPhaseEndCallbacks = []; + + // Clear state + this.renderer = null; + this.messageBuffer = []; + this.initReceived = false; + this.pendingDeltas = []; + } + + sendCameraUpdate(state: { x: number; y: number; zoom: number }): void { + invoke("handle_camera_update", { update: state }).catch((err) => { + console.error("Failed to send camera update:", err); + }); + } + + sendMapClick(data: { tile_index: number | null; world_x: number; world_y: number; button: number }): void { + invoke("handle_render_input", { + event: { + type: "MapClick", + tile_index: data.tile_index, + world_x: data.world_x, + world_y: data.world_y, + button: data.button, + }, + }).catch((err) => { + console.error("Failed to send map click:", err); + }); + } + + sendMapHover(data: { tile_index: number | null; world_x: number; world_y: number }): void { + invoke("handle_render_input", { + event: { + type: "MapHover", + tile_index: data.tile_index, + world_x: data.world_x, + world_y: data.world_y, + }, + }).catch((err) => { + console.error("Failed to send map hover:", err); + }); + } + + sendKeyPress(data: { key: string; pressed: boolean }): void { + invoke("handle_render_input", { + event: { + type: "KeyPress", + key: data.key, + pressed: data.pressed, + }, + }).catch((err) => { + console.error("Failed to send key press:", err); + }); + } + + sendAttackRatio(ratio: number): void { + invoke("send_frontend_message", { + message: { + msg_type: "SetAttackRatio", + ratio: ratio, + }, + }).catch((err) => { + console.error("Failed to send attack ratio:", err); + }); + } + + /** + * Get current game state for recovery after reload. + * Returns render and leaderboard state if a game is running, null otherwise. + * ONLY called on frontend init/reload - not streamed. + */ + async getGameState(): Promise<{ + render_init: any | null; + leaderboard_snapshot: LeaderboardSnapshot | null; + } | null> { + try { + const state = await invoke<{ + render_init: any | null; + leaderboard_snapshot: LeaderboardSnapshot | null; + }>("get_game_state"); + return state; + } catch (err) { + console.error("Failed to get game state:", err); + return null; + } + } +} + +// Export singleton instance +export const tauriAPI = new TauriAPI(); diff --git a/frontend/src/shared/analytics/AnalyticsAPI.ts b/frontend/src/shared/analytics/AnalyticsAPI.ts new file mode 100644 index 0000000..49fd183 --- /dev/null +++ b/frontend/src/shared/analytics/AnalyticsAPI.ts @@ -0,0 +1,24 @@ +/** + * Unified analytics API interface for both desktop and browser builds. + * + * Desktop implementation uses Tauri commands to send events to Rust backend. + * Browser implementation sends events through the WASM worker to Rust backend. + */ +export interface AnalyticsAPI { + /** + * Track an analytics event. + * @param event - Event name (e.g., "app_started", "game_ended") + * @param properties - Optional properties to attach to the event + */ + track(event: string, properties?: Record): void; + + /** + * Initialize the analytics client (called once at app startup). + */ + init?(): void; + + /** + * Flush any pending events (useful for cleanup). + */ + flush?(): Promise; +} diff --git a/frontend/src/shared/analytics/AnalyticsContext.tsx b/frontend/src/shared/analytics/AnalyticsContext.tsx new file mode 100644 index 0000000..cfa8c5d --- /dev/null +++ b/frontend/src/shared/analytics/AnalyticsContext.tsx @@ -0,0 +1,13 @@ +import { createContext, useContext, type ReactNode } from "react"; +import type { AnalyticsAPI } from "./AnalyticsAPI"; + +const AnalyticsContext = createContext(null); + +export function AnalyticsProvider({ api, children }: { api: AnalyticsAPI; children: ReactNode }) { + return {children}; +} + +export function useAnalytics(): AnalyticsAPI | null { + const api = useContext(AnalyticsContext); + return api; +} diff --git a/frontend/src/shared/analytics/index.ts b/frontend/src/shared/analytics/index.ts new file mode 100644 index 0000000..9a52f63 --- /dev/null +++ b/frontend/src/shared/analytics/index.ts @@ -0,0 +1,2 @@ +export type { AnalyticsAPI } from "./AnalyticsAPI"; +export { AnalyticsProvider, useAnalytics } from "./AnalyticsContext"; diff --git a/frontend/src/shared/api/GameAPI.ts b/frontend/src/shared/api/GameAPI.ts new file mode 100644 index 0000000..6e491f0 --- /dev/null +++ b/frontend/src/shared/api/GameAPI.ts @@ -0,0 +1,100 @@ +import type { AttacksUpdatePayload, GameOutcome, LeaderboardSnapshot, ShipsUpdatePayload, SpawnPhaseUpdate, UnsubscribeFn } from "@/shared/api/types"; + +// TODO: GameRenderer has been removed - needs refactoring +type GameRenderer = any; + +/** + * Unified game API interface for both desktop and browser builds. + * + * Desktop implementation uses Tauri's invoke/listen. + * Browser implementation uses WASM bindings with JS callbacks. + */ +export interface GameAPI { + /** + * Start a new game. + */ + startGame(): void; + + /** + * Quit the current game. + */ + quitGame(): void; + + /** + * Subscribe to leaderboard snapshots (includes names, colors, and stats). + * Returns an unsubscribe function to clean up the listener. + */ + onLeaderboardSnapshot(callback: (data: LeaderboardSnapshot) => void): UnsubscribeFn; + + /** + * Subscribe to game end events (victory/defeat). + * Returns an unsubscribe function to clean up the listener. + */ + onGameEnded(callback: (outcome: GameOutcome) => void): UnsubscribeFn; + + /** + * Subscribe to dynamic attacks updates. + * Returns an unsubscribe function to clean up the listener. + */ + onAttacksUpdate(callback: (data: AttacksUpdatePayload) => void): UnsubscribeFn; + + /** + * Subscribe to ship updates. + * Returns an unsubscribe function to clean up the listener. + */ + onShipsUpdate(callback: (data: ShipsUpdatePayload) => void): UnsubscribeFn; + + /** + * Subscribe to spawn phase updates. + * - countdown: null = phase active, waiting for first spawn + * - countdown: non-null = countdown in progress with epoch timestamp + */ + onSpawnPhaseUpdate(callback: (update: SpawnPhaseUpdate) => void): UnsubscribeFn; + + /** + * Subscribe to spawn phase end event (game has started). + */ + onSpawnPhaseEnded(callback: () => void): UnsubscribeFn; + + // Rendering integration methods + /** + * Register the PixiJS renderer to receive render updates from backend. + */ + setRenderer?(renderer: GameRenderer): void; + + /** + * Send camera state updates to the backend. + */ + sendCameraUpdate?(state: { x: number; y: number; zoom: number }): void; + + /** + * Send map click events to the backend. + */ + sendMapClick?(data: { tile_index: number | null; world_x: number; world_y: number; button: number }): void; + + /** + * Send map hover events to the backend. + */ + sendMapHover?(data: { tile_index: number | null; world_x: number; world_y: number }): void; + + /** + * Send keyboard input to the backend. + */ + sendKeyPress?(data: { key: string; pressed: boolean }): void; + + /** + * Set attack ratio (percentage of troops to use when attacking). + * @param ratio - Attack ratio as a decimal (0.01 to 1.0, representing 1% to 100%) + */ + sendAttackRatio?(ratio: number): void; + + /** + * Get current game state for recovery after reload (desktop only). + * Returns render and leaderboard state if a game is running, null otherwise. + * ONLY called on frontend init/reload - not streamed. + */ + getGameState?(): Promise<{ + render_init: any | null; + leaderboard_snapshot: LeaderboardSnapshot | null; + } | null>; +} diff --git a/frontend/src/shared/api/GameAPIContext.tsx b/frontend/src/shared/api/GameAPIContext.tsx new file mode 100644 index 0000000..0e7261a --- /dev/null +++ b/frontend/src/shared/api/GameAPIContext.tsx @@ -0,0 +1,29 @@ +import { createContext, useContext, type ReactNode } from "react"; +import type { GameAPI } from "@/shared/api/GameAPI"; + +/** + * React context for the game API. + * Platform-specific implementations (TauriAPI or WasmBridge) are injected at the root. + */ +const GameAPIContext = createContext(null); + +export interface GameAPIProviderProps { + api: GameAPI; + children: ReactNode; +} + +/** + * Provider component that injects the platform-specific GameAPI implementation. + */ +export function GameAPIProvider({ api, children }: GameAPIProviderProps) { + return {children}; +} + +/** + * Hook to access the game API from any component. + * Returns null during SSR/pre-rendering, real API implementation on client. + */ +export function useGameAPI(): GameAPI | null { + const api = useContext(GameAPIContext); + return api; +} diff --git a/frontend/src/shared/api/index.ts b/frontend/src/shared/api/index.ts new file mode 100644 index 0000000..be4ee62 --- /dev/null +++ b/frontend/src/shared/api/index.ts @@ -0,0 +1,4 @@ +// Re-export all API types and interfaces for convenient imports +export * from "./types"; +export * from "./GameAPI"; +export * from "./GameAPIContext"; diff --git a/frontend/src/shared/api/types.ts b/frontend/src/shared/api/types.ts new file mode 100644 index 0000000..d3ef50f --- /dev/null +++ b/frontend/src/shared/api/types.ts @@ -0,0 +1,72 @@ +// Shared type definitions for game API +// These mirror the Rust types used in both desktop and WASM builds + +export type LeaderboardEntry = { + id: number; + name: string; + color: string; // Hex color without alpha, e.g. "0A44FF" + tile_count: number; + troops: number; + territory_percent: number; +}; + +export type LeaderboardSnapshot = { + turn: number; + total_land_tiles: number; + entries: LeaderboardEntry[]; + client_player_id: number; +}; + +export type GameOutcome = "Victory" | "Defeat"; + +export type AttackEntry = { + attacker_id: number; + target_id: number | null; // null for unclaimed territory + troops: number; + start_turn: number; + is_outgoing: boolean; +}; + +export type AttacksUpdatePayload = { + turn: number; + entries: AttackEntry[]; +}; + +export type SpawnCountdown = { + startedAtMs: number; // Unix epoch milliseconds + durationSecs: number; +}; + +export type SpawnPhaseUpdate = { + countdown: SpawnCountdown | null; // null = waiting, non-null = countdown active +}; + +export type HighlightNationMessage = { + msg_type: "HighlightNation"; + nation_id: number | null; +}; + +export type ShipUpdateVariant = + | { + type: "Create"; + id: number; + owner_id: number; + path: number[]; + troops: number; + } + | { + type: "Move"; + id: number; + current_path_index: number; + } + | { + type: "Destroy"; + id: number; + }; + +export type ShipsUpdatePayload = { + turn: number; + updates: ShipUpdateVariant[]; +}; + +export type UnsubscribeFn = () => void; diff --git a/frontend/src/shared/components/AlphaWarningModal.css b/frontend/src/shared/components/AlphaWarningModal.css new file mode 100644 index 0000000..30fb6ad --- /dev/null +++ b/frontend/src/shared/components/AlphaWarningModal.css @@ -0,0 +1,164 @@ +.alpha-modal-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 2000; +} + +.alpha-modal-overlay[data-state="open"] { + animation: overlayShow 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.alpha-modal-overlay[data-state="closed"] { + animation: overlayHide 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes overlayShow { + from { + opacity: 0; + backdrop-filter: blur(0px); + } + to { + opacity: 1; + backdrop-filter: blur(8px); + } +} + +@keyframes overlayHide { + from { + opacity: 1; + backdrop-filter: blur(8px); + } + to { + opacity: 0; + backdrop-filter: blur(0px); + } +} + +.alpha-modal-content { + position: fixed; + inset: 0; + z-index: 2001; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + outline: none; +} + +.alpha-modal-content[data-state="open"] { + animation: contentShow 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.alpha-modal-content[data-state="closed"] { + animation: contentHide 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes contentShow { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes contentHide { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.alpha-modal-header { + position: absolute; + top: 3rem; + right: 3rem; +} + +.alpha-modal-close { + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + outline: none; + box-shadow: none; + cursor: pointer; + transition: all 0.2s ease; + color: #ffffff; + opacity: 0.6; + mix-blend-mode: screen; + padding: 0; +} + +.alpha-modal-close:hover { + opacity: 1; + transform: scale(1.1); + background: transparent; +} + +.alpha-modal-close:active { + transform: scale(0.9); + background: transparent; +} + +.alpha-modal-close:focus { + outline: none; + background: transparent; +} + +.alpha-modal-close:focus-visible { + outline: 2px solid rgba(255, 255, 255, 0.5); + outline-offset: 4px; + background: transparent; +} + +.alpha-modal-body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + max-width: 800px; + text-align: center; + gap: 1.5rem; + user-select: none; +} + +.alpha-modal-title { + font-family: "Oswald Variable", sans-serif; + font-size: 4rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #ffffff; + margin: 0; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); +} + +.alpha-modal-description { + font-family: "Inter Variable", sans-serif; + font-size: 1.5rem; + font-weight: 400; + line-height: 1.6; + color: #f1f5f9; + margin: 0; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +@media (max-width: 768px) { + .alpha-modal-title { + font-size: 2.5rem; + } + + .alpha-modal-description { + font-size: 1.125rem; + } + + .alpha-modal-header { + top: 1.5rem; + right: 1.5rem; + } +} diff --git a/frontend/src/shared/components/AlphaWarningModal.tsx b/frontend/src/shared/components/AlphaWarningModal.tsx new file mode 100644 index 0000000..9eec8b9 --- /dev/null +++ b/frontend/src/shared/components/AlphaWarningModal.tsx @@ -0,0 +1,43 @@ +import { X } from "lucide-react"; +import * as Dialog from "@radix-ui/react-dialog"; +import "./AlphaWarningModal.css"; + +interface AlphaWarningModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AlphaWarningModal({ open, onOpenChange }: AlphaWarningModalProps) { + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onOpenChange(false); + } + }; + + return ( + + + + +

jU?lMi@tgxr7CqX_r3uw^y4tVU3Pm0sw;|1WSUO%?=bG`*Kmz6u4{#ti;T7AWIBAEh!(Y zz>O01&#X?Ds@L)Sb{CkG#Yz4$3o d@96)?#cz^xWoA}>B$xmI002ovPDHLkV1l3&k#zt7 literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/Square310x310Logo.png b/crates/borders-desktop/icons/Square310x310Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bc04839491e66c07b16ab03743c0c53b4109cc GIT binary patch literal 8591 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 JPDHLkV1lm|RXG3v literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/Square71x71Logo.png b/crates/borders-desktop/icons/Square71x71Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..63440d7984936a9caa89275928d8dce97e4d033b GIT binary patch literal 2011 zcmV<12PF83P) zNQT)H*aaHEvPo@cmXa#lOYSVWlpR1nAeK#0OX|;=*_qi5z??aA=FFLM-4Sq2kUOhO z__7Kf+yUXO;t~3LY3h_?kg^Ly_=vx^#d`M`3g*hiK~ZY3AT~jwFz3ZcM?f3JYN1%a z6(!V_i6eLKHt^>r*a)I0z_0NJhQk($6o5l!E{?JkPrSxoeQ-;Fqc_D`_YF8=rsANr zG)LA_971eEG~9CGYBLi@?p9m)@)Tx607JQ+*Ue@kj-@a(D+T!4#k)I>|5h&OqgB`h z?c4$tE)KfVHvW8WK2f$Y7BwM~AJbeyzOSy~m#(8wbuiN%36#mj3KfSHV@MPU&upJC z26nV0*ffeHL`yvW^BH8IFmcq)d*U$Vl;hFt@(S`@2NOr}7Sd+Fp?rbjZ-XVpiL+ZJ zVf=)*k4NU-1sB(fAHUA1R4M)eyT=i=ZEY{1xRDA;0LLFcXEjsGBO-LlIJ_9C(9GAXuL zTaWXYBX?I{f^r>rHH*sm()GzY;)y_KC4pG$l!1wRaq#9`i86Kr+wt%Lp<83lq@x7B zc+~kD7&vz;-52pYhf9^cUJaN~#g4OG2QA=;{?W`wITJf(pw%Y67s?G_QcOUGi6G6& zes8BV2#>7foT{<4uXDpmrPUS?Y#N*Dc@w_-L=?H*HrkF$d z3#j0$2Sp3K2%hvFtymS9Sa)qEdq;w&zs&Xs0O0ycQ zotoD}7%D-MawgdX3vAu0raMUP)Mv~{MWbR(S_xv|QUu#_sO6A2bqlWvmiXwRRCa(P zrkd;tCrIm!27Jr$U`;uIDWY{FbGBTGA*OV zaq5*ndh8t-G|j7}W|J`FP8pl}HkPBUggH&DxJAlnPY$8scRI#6B;VhC88^|5Yw+Yw zFCZhin_c2;@Q?8%idU?`0AtcEb2~yxj9bROOps?20l^aI_TFE9(tF{z-yMMgA%zc2 z&=P-y{B&LH&tZx4DR**bcD>1&f?pVFQJX093q$1Y1bU|txk2hWkd(uZoI-_?$%A_< zj9#-AT7##pEbqV(?3jbINuVFV+y(4ETyBH8=ZjV&T43g4Od410WtYMbY;mOUw5}mR zm}em*yjgmZBrt*Rwfgs$&57DLxX0`84J8Wpfr?mqW>@9Q`v=b@3@>-;s2ay^AGb|G z<6sHfKvDhCp|(Ve;bzEcvl3O;*J%g4%2fpH=m(LF-ZdyZU1QbHsqFQSE-uy)Xaxb* zSL{BCOVmU2;8(hf{{5BA37-zT*~-HPxP<1#!&DztK74BQf4R+BWyl2;uM4NAH38ll z)?^!My^IQCPqXx!6D!LZt!(O(KGg{Rd}Pcg?FQ!DagHC3ltZvYG*|f@ACA5 z(y$gMwjP<7kBkLc{{3_A^=#U;p=LeX-Jli8g)Q4S zGsR5xg_uRQNQ?m0(5Dd4a{mz+l&#zm6l9G~=l9G~=k}HOSD-3Se z=jhwnuK|Cl<(>yq#FY^_60{B#=L!9<4oE+T!cL+`@6H3nF8HuR!uOycre0(cw+R)s zrXgw)9=+XH;QO7tEq!W5CUINfkhlOY*hZ-ijQkgQi9K~92bSxob%4Nfvqh88H~~nx4}GW7*L4jK^Py8nIo~x?+DryN$BTbk-|idT*N-e1Rex&uYxV8 zs;+vp|9Rr`zilkh+9til7D(?B%R(0-awITYu&enHvQ*rlq~fJXBoGMhV~fOV=|9Sz zk1j^!w~cK|E}ELFSzIe&R%qSO0o{x1yR+jkFgySCIvN*o&;lgREZ5PMw8rCoZ%QaX64C6^AXjaDf@M)O$fvw-Xm4 zt^`?V3UU)UuwtamC!Smc9uo<@k+`s;bllrS^0Va7iZ6r1vL1bPqV(2-93i1s$!T_D z7tto2#+s{;0~f3~jCJXYVqMD{n-L>?PJ6{s>>3BCj-7BZCXma<7nLp7)5N-2qp=YV z=uVqAdF{DaGK9W%ej3I74qbe*Ru1bXZOmb3#=x4dbdQe->(6ixLJ_>E)#QNzWXYcvW6ai{SG;$nFpf0nwv+(Nj!yGQQA zUjKFVWcY)R=mSTSED7eq+Po4|hgBUmOg zkxAe-S?M+cy74QOzJD{YBEl8BjD+U{A(=!MwcUdbDtM-|mVC1Zx*)wlldbxix&h}~ zRB>33<*kdnuy;t-t6PvK<3wNI%9No1-|!#7YMWLcVAWl)1%p7~kc$3Nj$`HYL?M?0 zHxgEOAjF!;?1ND$Ef*2drN7=hd~o}v;4!>O3aweAlzARE_O}LilNFK4f?FK>YAxny zg2e4Vs4e$@uZb#ffkjd|RPYdw(%@GhA!(do1fM}jYLPj~0OjZkyfM7?RV?ngr&#W7 zX>~NBj1Qz>{1lVP2ySYTM{2Z|9H#MIhAaKWJF8x!k$U$IIvSxxdzUT<8vqS)N*xyF z<7b`?NEKahvOxm3lGd@nhY#*Zd~YHoV28eSq9K;?>@rv3-WZouE6y`|u9yYXY%m~Q z2&dzR6|@f*?FxME>BG)S>h6kG4^pWuFu>SduoXjcxYq42)?UC>ppv++c&4o~W06%- zxJK2rAr7q$?q!9R6{DG}V2niO%37i?c3{JM_^St3fp9J_9t7h%(n#c) zI1GAp+(Mf4lE_tjdT?hR1hBxA)FjuQ$)d=r+mM2As#CFx(5bUnnd%h#WNL!Or=6fg zSrK0}ErG))U%UPO@26l$bbO7cO7#j^KK@~2RzxhaN)kiZv!lDBr6utA>3wGtgs`~5 z;JIkJAKSK$3X4VN4Jr2bC=;11U)JbUFc&34T41-n8HlSr*&jTr9Zr1O!FrERIr{b1 zDBgBKiUUj9Yo+yH4%aLS%;Y-+{sXhe$40FlMCA&W3q&RhZuYEasfCVd9na1V$R~po zrGm42x@cZVTpyFZk|kE=HRcDjk$NCS2_`F5;_C^+w2TC1x+ucV%B0sb2s$ib9Bd_un1t9}B+W_q;KcXHeqea5`f}#vwDo;9E(yh-Bp~2o zJ1Nz{OB2MFJe;k@UUh{iN*35uR)R_oo=Nz~RRkam&4m)cMMec9L)|06# z%}rAOmFG@q1~y+tYxV$h!wE+OQ_4x7-z({de9*XF4mQVf1=dWz@46 zg>a{{Gg}lEOcsz*-|DxY^8T0`EjT4#cz?KFJsuq;l?ZHMe4HWCWw13vwc$OS_n<(= z7R%@GcvBwlB_<_VQ;ah{M0~}k_$Mx4Ylb1a6!{cSN^b4;TaLmf6tUFtWatK_6f^cE&b_un2M|G?W_mkF9Cw)GzMsK>bTBr9#h4x_TJ_mxiyvpcx z(mHY#ojg0~sYK?TnQqBW;=&w+W((Hou&^&4;V9REo74rO)9W*EFf?P;`-M{5ebqtk(uz+ljul8XxR$4c;uCf zPh2p%Y@JJ++Klp_Aoy&xO%M?I;pL*n#;l6Wme+33E;?q zyB_qeHy|InYJ`nx5}3)GqQV0000N?3#xh7$lMzK8K=2xV( zktZjJ6YWNPc&1V{V~9QO?wPSoe)&new!5c$`gL_xy=nl)7-I|@5S|!RE;#(*f`XTT z%IP$>fC3K!xWbiM1xA1;A;OEF0;RS9X&Hz~*wF&SQ}Ba5Cgs6^7&#F-f3wB^@9@_t z$O^=xK?#kFNN9x|9p)QaAUVyy&=;T|sk zwhJjSG?B<3unKw-yl^_;g;(&W>UnIOJn!-fHn`t4%wEFf+A*ZS@I>Cf;p0RlP0s;G zB{}b{#5u}^5^sk1l@se~@i8l=@tL8BbQW-^>Dl6){24N!b39M@YXN#!DArs_8n0j& zM7tPYQf3l@aMuHp1$({Ify*S_r11k239S(w1##jdA;7!m4npDq;V}$oy{{vu+pySJ z7!XWki(gQUJMkz$=Y@S<+E!0v+E`2_>}$m~UZ zH-FM*u>cn2AtPR2G@Z6;pKvrONJx2ntwR0z zRj_HCj7Ti`&d}?{ep{75CX38{XcpSwS0fTBLDmIK(TCzoZBGDy#h(QWQWFtNkn+nc z&HE=LXekQxj*eiAG$2mDRQ&_=D~l7fDuh%-goKX<5(vBP$9+U0P%XB-$mzC<2akVu51 zlgo=P^}d5VpZt~UrEfh*fsW{#ruW6=u)(J*o0#lK5~p_(u+}HZ7D4Ej2dH+vxAPuk zL~0d~!_BUM7$E@bSgVhSZvgbx+-!}b>xJ1=HNqeWHC(*PWG$B@<*gR+F<6baDgVwY z3MJd;Z`$GcZY<7KAOo00fqkhzNfPWOjkQ{Ykla{Ht-kb~(Ya?X8wdH@_Mdzl%kqzZ zH=W3;i3t573JATCF@-e*3E{UlQc00xdQv0{%aqOD$H~cY*mkN_V=|LcnYGw~mV|^{ zf^A3vJCRrjL^8*6MBLD}Gnr?%FSLCfE3nEXos98pqB4$55+y*To%Hp^?@m0=^o#># zlQcSOJ&^DqC59_?JGhygkor0+MRoPyBssdv=ttOB9g>F{=5yuOz}46V&w& zb7%Z<1{okpGn%*@BeMw&Uq4`weLC;GC04vZCMN~FHmn!ET^;!t{M z=&o?zkssvFyM5mj+0|(Jpy#B&oYVj^Dir- z2+^5u8u=)#@r}uT;vy4YOh@+p>sMuNwv2% zV`mX&0RVvA!ra6W0KlhHFaTpb9S)*@kxmy`T9_C*N9S!&S!d3=xyV1=_B!lXe$8uc z4wlWdGBTItapnO_-~O!KZO(TF#Q%JBHz8%{(mp%(X-@^}N}rvXgUL=pRL&DHONu#q z=N>0>n3?2~bOw~i);4&Vbbp*ioNJh{Q z^{t-yi7pEDX@5PJcJJx`oBm&qgRyWqHl9?otN8zKrYldLFZ{vuVZqFLDRE$SXzz8+ z@Z4e4E$W;7_(v|EXWtPgpLRY(eIGQCA8W`Y+ZxyO+`n*B=^SS!S3 ze^OWD4-VhhKv(Vu4+$}MnFC)x7$JteaQkTLyX@uv?dYPeY{I$qjAF*c%sFvCSwQ7- z%icb+?_HtyMC3tBvEs#*#zmbCd?WU{M?7|MH|E8rZaO|N=_VhFk-o7~yyd80-)7hnVq7j=Ji?5o%544B;xp(Il zD4w~0H%NP@9N^1~Hmqi>Mkif3$ zN8x|bQoAK`TG~0&clT#-we#K~5@e#%+rGB9eV)-BFXKB(Tz2Io)n3>GnB$F3v5tW` z8sSMz>th~{D=9)1}@ z3g$b{MPBt85o0-CAhXGWnu%96nSq_!!>dM6Z61vr*vR%JO&-ZifMrDoj4;$^+Bk>_ zgtz2FLYQ~tq%)_nGT@`%;&>@pbXLkilx*L(EVPoLIZgxt7ft{8#}2srLc`t><74cj zLYW0qw_fncrc;SJmq*R2t2!8A335z1LZO7=yX%j+p33^l0*fmE)u7mbg~GS9>(^S< zLxwp{4_e4NxopE5 z@qSLnC_{#M=03^OtsiUfLYir2{~(^DZMi@aDJu!+c#I~eAU=I~@eL%%-H$<~>4lQ( zme&uomBhF~MKsd-wLS#(Auidp;L zZ&i91s%QbjT^}~C9u8Xx@D!H!CCET>pi8dQnRuNH1zEHWuOtt!omv8RNJ5bG?sHsr zY{y?=G1&VP>rIEy7h8y7P~R8*ICI7;;Lz@bc(q@{5061B_sr>0K1Y<0W_n<&L~O0o z)*(c9fb^*uh;gVU7X>CT1b`24+s-US6sb}4;u+=);K7Q4rVH-w_du4g%7>y-8A&MQ zK3z11aI|^hGqv>-!zS@=11M7f$D2|2?ECU^KOo0&(9H1+L9}qv%mjeAw3|1_SiVsr zeznoRzDe)c8bHlb=Y2@|=`$myj4cOXnKMGnIA##Z3o6+(l}uKrQkPMEF~r&ehk}UT zP4AzRK6xMl17v+2O0O$23so@@fGBR+LUoX~xGdso5mAmwrx;hpDqB>jSy}-xV+kul zT8e(2u-I;{_=JES^HFqm#KALpKnAbidEYtK<8QHiGcjFpx6aC2_rs)M7ysSc2@uP~ z6q!i6nQEkE0(W$IMi?kOD?OH-?$_XhU>*g>X=|PlBJx%Y-XjIahvVcB!&bsy%uvNm|R z>WU=ew>1fBz9g6IYamY=P&NEiTS>iiUh4eLUHIXv2}dw`dpY9&gQXEd@jy!$Q8UB zWf84B$mI~9iKbWMn~qwWD-gN9p`tRN$&0eSu$|5=E%oD&`wg|fkMe$l2d;#GHJ~{H zW&DJKHxHq|9^}hGo|rQ&9l^abfmLLBvPK=J#fr>Pb{n*`4khuSaETk;WKo7{CN9kd zT}VYZ%lCt#gO`#Ljt@O+;t|gQezuQgiCMOWq&uU#0e&*%?bmILDS$j+dC8Li`L!R&qAAKU}BIAVS$Nx9FlJFikZx>c`}s2 zVK*hspd>D|sVPfK74)Mo)`4I)9EG8v$Ked|HJV)gK(07!n7q9y4VL;hI@4HMVZqr( zUyP!1ICF=ZptFF==07PHPjeiz5e|dmI9_kaj#WM(XQN$s8UGanPoz&jF!Cp;KCWXh z1@_~$_)2|oF1kI)hodgM49#QM4}#n9pB*??r+?)+-TQ+tmoDtFtWu>;w<$UH0FgH;7! zcsVH^X-pprYF-u;6XR+C@t~Kl44D;%tcoi`mS9($r7Ln?iWi~;U8&q2*Ne|!xQ>y5 zx6wag2iz=aD;IdsWdQ2)FbK|wdbb8&m*PZyt2rdmHk05_p?uBMOBm=KMHmOKF^`z7Z5-3p{$M4_ur;(#Ocd}y++ZQ&{JRn zaq#l3a$LwPsbh9brsIMdnHxhumm5CkqT?V6Q?$j&bI!%K5dy>>l=lVgi0h|e1UkVPBMS#ma zEO5mpN%d`TF3_2ZOX|WJb`KFgHh>BE1qNzPj?jV>n_#}Qo|$6dWQbaA&;caCYsfrE zWh$5Vwar2So_P@8;_MenKXKT0DvY9iF-~w+#EHod906>8TaZ zp-XeI4mL>wqsWX7tO+A20KDSAX3RmlFZe@;+46U{aTjVbX?j!}28uKRw`?T(b2Ee` z0qu>s;f0bcy|M|9A%U`Jo&*`*$b;WhGt{;SmijF>;C;166~mQJ!pyk0nLw~E6YcBE zy=`wIozk85vy*lr3X1@dK9)in6GU&)w*)@%{DYxC-H^!Qc=@pKPNR0H0AX8YFB@jG z73q1?a9}%%J3;MyS37Y*!Ru{%owFDk3Xyj zboWC*D&VF%VkV+d{L35=;2>qCck=Bed(x3dYft`xFdj*mhO2fdxLZ1m!55j`Z}Lj5 zQXjow9$N!ap$84O#jBVnZxfg#hdkJps~EKj!!B$GtEw5-28X4^d&!|Dh>t>zMe$Zc zBzIUi0c*p4P$|4pBAC&SIdDHbU`2Ery7EezKq`EIIgTlGA9bmmp7w5WU2M zXtJoL;bTvR^|#hLXb!cR^2buLl4ii8EFhKb>}9b~a+l-m!FcR18=vN%`W^d6wawFz zCVWBL5e}o<^!MarxwfXaX28bTXP2)A?w-3-4{7W%s6)0sBNyZC>mQajDQ-n$UW@8 zGN~^sJM7A0t^~3W)W|wD_$>5T2Tu3wM{OP?!#hQ+$+c~&%oT6ZLzx&;W=Qf|@RoLf zXg})Tg$agG`jUT$YZJZ!Baiu#?7$lF^|yTd*}LlH*rM0*FL;mwTjw_3c*{YiY8LP| z)5Jlz+wEiW=Fvm(+U|lkdwwk;+K(bB+Lt?M&EPglIdNyVz}l{?!SO@ik1aQ=@+7D7 ziTO)8-cLfB@w0cEsz;_$P_0~P^%1szhrb11kfucUYk>-zqXsy{BOVlOwTIZ~A4im_ z8TfnUhpnkaGG@RkS+Bc&6VE2r*8hF^R5BxrdBzha0%ayag_#M^g!_{LI2HOIy+mGE z+Ulv}cZ7F-E^F^#Y13qKExjZ+ABkxEJHB_&8v0Z8#lW=D)nA%t{Ebfp^B-6SB#|O3R^59ZCTO!P&AY>oa?!7 zD$FkQEb%l*t;zz4@S08fBL(^|kzb?^@^|01mzQ@31sJ=Ro0kdK59ibIO8~tp9pxc* zc`StCY-Fg&`L6J6je;4$a~4D}{frxJ7M0EvFRDr~?=D6cTme2Whm8X6W&Y`z&X0e8 zuQs6Nx5lrB21m4AGDy~z9trvSNoA^N`GCTn3Rr`VJ+dW2Hp1t1V!=|{bSd&>P`lk< zK#OCon%R5~zAy4H2lyoTwS~(XEWfrA>2sNqV9jK2YlG0exC@4dcFyTG}CRhl(axm;Lc=h`A4kf(C}TIO5mO0yhI?6kmh zf_ggNIX>)F+-P2W;c$T8{*=FVopYv0tu@pVrZ#iwcrpsvad0W+4V&pz;9ncg04%i8 z%m?tpI7S(sCY@ec+A$JaL=fFyZ$Gv+l(*@XoB0G>Oyh|>LKqAT+sAXWgeqnjI{3sR- zf=!3t4b^R#kaNJUGQIK+`IFZ!7G!D=X@c>#l!+|M-8gC(dom9Vn@&Dx+!o}8Dv6;7 z@4H8Ju*IOSM?!NABD}n4{bFmBaN@vCNdEk$Nvq-ma-?u~4?wz}NCUjMlGvqkU= zjf$N5{O4T0g!1VJtN_!2*D%OHfh&(;C;1(%j0)Om?gz{mKPv*i8BG$IwW3UsllWI? zGq)9NK~M7xDq>5J+D*}6y95O-nPdRKWB?b zNiqCmyZ+q;Mwl401lrb?VM(RTg-Mb#q|TGFT5%B-=oPRA{Maf1&OssO)5SO_6C;)> z5V~mw+SG+fv~~Gn(-i7^t3g?s=qrrPZRMzq z&ZAS{*PcNor9gbgpaZ#`awtL?Ebufah~uM$Y~hoL8I8f!PCC-9Ix2qU$wKc$d0tvV z2On+N6c8}vx%CW8cpi^cL|nw<8E$t&Rhfa)z+)8JRt1(N*!7~=CO^iY^hTFkrtkIH zmp=gCFH3jJS@I;9Bq4{Zk6VAJ9rF$*>RmT45JY<_e^>dnW10BxLa8j!_@@F_uRdK} z5c=)g2@7~W%GZK%kG-&Iha~HW_Wtg|6sr2Ds6Et&=ad!71lVeJ%L(u#=n^7sE&|QR zeB88NX|+(-cwU>l1}BmZJYFP7aflH>-A z_)6R2=HUn~2+P3Xis$wIF0SxGDQ{k6O=`0--P%NQkEswzvIz8@i1izJ)Q5q2#yN)Y zpz-Nmf3oXP&Qtx|S3cR?mgTc$z)Is}0T}Kj2iMN32_sEu((Y($w)K`BI5wy$O0zXo;XiJD|Csl;V34Nw^ElH5_8Nxnd+RjgHFf-P{9(&Phu3T~{r;tU zXBaiuTU-XzeRH<7{&aPCvAg+7yq`AZYm0Z?DaVQxLuf17^-aZzWM-9DJn`}XAPwJkW}`h1>=Y!b3V1NjJFdQM9}kdX?c}CzPA>i% zHY3I|8Tn3y3rJvh%tHBaNsC3JI)Q|#QTdIMQKpYKakLjL0fzl1oe!m!@6=D7Tk`B) z&c4DVBmsG_@S7$xJ^VZFr~Ic7>)1JwaUO7!>$uo5JILO6OXN!qgVEhMSzJ*1xgYwE zVz#>_hL5H&xlKe)@tR*u@Nkp%#S*h$9r>2|;r}@HUOm*|M0!)+G`!E4f2}$q`YZ0z z)EPvPBH}aqvin(B(h9EK_A2>>KXMsa1&{7=t9{+EeW2tu9WygGb%I19^{op9AONea ziKyPZ6L5S^>jbnz|GiD_fWsrbun&owBFq^{n4UKa{h3MANBH*!ButdqLWf$$pw3p8 ztipSA3l1Cf_D0AA%TKG5*~7S+IF;}BGgS)R8QoXnqFbulp8Y95Ti)sIl6)_78r1?oucV`U3Q^C9t|(vKK>J`Ye?JaQpJD<+kmN;!}DP3l-{?v3zS2cZDTS zwwn1~@g1oz@EFFm|5#+=La9j&*F-kGN|)riiO;=5CNXWhsz-lST6^j=@y8N9gJ(sV zt+}9s@9AErw3A-Iy2G&@^E<=gw+u_naLl#4!!L}Gug-Lpof(j{ME=Jj?4swEwyD{ADCg3-iaB5P>Y~;}Vy5zan1F67h_$Qu1 z#R&g`SeTS=58cz->-G?DnZ9ZsWm7!S9id`i+p4Q6!CEZQq@SO?8M(p(MbSznz= zb^;Ch{~irL=x|i7zIO2yS^L*8vS4L@kxQ@j>Lm``<}!N|$n+`QcB!4v5$wcppkLCb zDVCY^)<#?XwRsZ#E+zge1kOP=QzqWH_>W^gp4c?n*E21t>T3bS+WvZ_nWn$rz!~-C zR^Pv-(fL@Byb#~`UH3vk5#XVHJisdM$(k<@W_e%CXN(z&&0|S1xSGWj&~y#Q>CSK+ z#d$k}1&x}~`qwCE`cH4ZhaUX~ql0OG`7(vHR|xfk8mt~?A&2Zx`YR7 zASkZm!UTjis3`|Au;GdkJ0>P-b;|dd@fN2417bhFMj5Xqt)yeTs>c!NAz-NC%*sz=37pn zjpwpSnyVKNJc{|-Z>xasRQYDqrwa!&_O^>BQf9b;FHNtW`LAo50@d^t&xhmjQZL6V z?n}5a7e1DKu5lntaAd$J{U;3>jqxdM*!~RV8X~HFLFG=W>3lUhz^MEb`M9_IH7ai3 zV$BR25jOL@PKLdU`e;TOJIlnK->)L+ClU8axg+ApsU~LQVA73?Ib#NF_o)iatHyx) zOI13iZ+$PItG0?C9Z#5};hfAb`_8Tm$(SDQ<?&)>k?a$RAO}R^keyZq&NYIn>EDLMoa2w2{4A33MoE-4$ z>(7BYyDVjdGQEPQF#WH_1AX)*23nWWTkBN`x%w>suY~>Q5T`V@d!?-00L$0?EZ~~z zX`QiQ5zDSI$M~mHp_z-tMdB9|qNSnd0W^XDU?*9__J8+Sr^5mIyk z>igxoZIxYl5h?JPjR`;2Y**%+&OZ`oX_!25nc5_ zWqf`D`1+3C%@}n7Oa3)rYicKi)%=>`6AL_lJ=ah_-FZ=wfnboHJ}ubdBL{Hon=NNr zgghzMkJp}h)~!1h!=t83rE*1m_PC_|ms zMbMpHTlplB4)Qg-=3RB#ZV+3I^;tkHx8>_of`YQ@)9KOvPb)+)ocdacxQH;Y-U%q1{pT`mF}!^Sm!F{T zMNM{8l&1_o2X3>^duDS9n7+MIvtbuo_Da9QQp9?k=?GUC6Qgl7ERyN1zt?C0B~?otAHaok5)tpAtf1}Y%Wo1ilAv3 zHf6kyQ%m=rXq;3RuBCN#43c>ek+Dq;Tf*MUpkff1Ki5;5hq3n3O5Vt^-r1`e0Wz$C zN|NQ7m0nd>`mVB+CE7weftn|L6z0^imuyY{J-D*_H&$pzD`&>E@1wrFO)O*)?xP~h zR%=Xv2Wb+rFNucBCF1w$X4gt*;~yC>cRC0oCyJ^66niBKAUC+EG=`J756l^kcQqv| zTk>d8dmV>;*f`RwkirK*Y;5rh#sV%Sw87ta0m|Judi-($*^m9gn#ezVTLdnj+*wQ` zsLy2ykxGMa%vvr7WI3JO9XraKXJ)_Gvh8`%NX?dM#El_;KWO-3;%aDqj~piAn$ko6 z*0Xmm$jdt_U4zj}s(`XIA16s5vgQ47vmDi1iXRBXs7+XW^KdA8&8fh4Hc10M`>09A z@lhlwOF(kk=w%BeD+N&u@g0LZC>NRuqkl4+%f*ITZAMKumobbNO`#2-Ql-$2dGC!7 zqwnO>3~TuZjfp=NS25`F+&yFDFbzWx@J(@6h6TFWEyk} zKB%>ULs3`Zhl$HR$Dc!DQ+HLOF9bZqM|B>9hfKj+Q>c2M_2xIMLh-yx+{a?GTNiizz9@eB*%{cWuExBF^$A2$vVZ-)B8pzq3EWb+YNY-VmLMHyUW*Sn7h>N_#uvjenHEF*)iK{`% z$D60Kq4puaM!UghbC(?Odgv#xOyN;0Wc99U&{U47&GX2YHcCSyR>}7IGYbKTW6B&? zig(}LHKm&K=!%3K@JhCDfD^c(WhF0vK@WT#_5MbE`K`aTMzWHYOc|#QHK>hq-Fqmm z5-{iAaR13!CvS*4AU1iu-;leMPp8JpRRW^=b2TNCLq4`^TNAbcgKPM?rd#j`{Ot$b z&ej<>jT&tpFgnWrm~T`~+Jx&F&}dDSJ~SV7wtN4AjMlr`1j8_F|dJz&N{b^-`TVF!9d3T<<(yxAoj>LXOj>bP<{b;q} zUNkk{VPtxI)Lb0kMjgd3a9rLVRe4X_wUjVH*0FCnNub41YL~Gq%6O{Nd;XC6F%{`_ z6pCFQZG)f4`VeaCKK2w2t5N7_msvl!CWeY3R!P?-9j zpT2PDzd$~iNxr2UDi%FAzLRCFtY2<6krVm`B2a?^>6?aYHP@gcsqz7k!xYArVH_VgC>Zx}~MP zCQ|MJtlznXm1abo7r{ct?Qm9FBV~9cptEpnLLPY*!}cmpP8xijUKI=v|NE}s@n>bp zsI_w`*rXj+aoly046r5F&P7sz=%~55u*-I=AJ%&uWGT0tfYh%!59^gO31m6f&XvOS zQ-1_mW3>EJ^oqtnp`}H{HOb5p-Q^Fuh3(tlL5o3G%9mA<*0G!G7p=uX{+i!J-hSg@ zDQX?QCBQ<{n4@4~f9?Bp_{=^iTw|0u@G1_s3Y6F4Bl5uD{2w{eOfWPd+gxBX$J`3wv26J#dmTwghWu+(UZxYz|qWh8SSot&ghzr zz#%NHC&XeJH2uN#Z6|X)8x{hIGTA6Kg!x3{|9N$9i|Bzgn2k*&FAuTlsPun(_8#4{ ze4)Sb^+oPtVZhjl8#XzLq(o&`oVi-*WaZPp40-8S_~V2L8fxtcW1qh5-U8qLOnZ|2 zi@rZlyDJNn8!9RF_9mH(><|-SU<&ODt4-nvd3)AF?`RQ)91T}x1ei05f&b}FM)^r0 zHC9en8O@F9Iy|^%-+r9_NF$wVF11f^5_VibTBr&}Z!@*v3CBvYZY^oA0YcYnu)@%IWk~|X;AkadOz8qKS4$w)O@iey1SS6 z{2;N1_SUv%897yOBcq%jwBw!|b2l)jCzAK0-aRK=;q|3{32!ipXRTZc88;mbj_$g# zg$`XRmbt^)qeGqV^F1ngtht{$yWO!4Ac2q^fy}Wh{0J-mW^;!2tuytq zr%WCjlAr@bS<6amJPd#^`ijIL)?(SdzA*w{o&kG+c}!DM7}2Seq?yitV&JIvmH89x zyKhjHr-{&w;j}mS&1@q5W*45ek{&I ze@rD0Dy>*0A+Ba(=y75(qbl6JUUJ|mwLm^=7bT~6AIKv_D{0}+*yg0p$#XS|ALr*x zp#S!^WTz0S2^Oiobqp_(Fj+hH(W2edojf`R7bs<@q2*-R;D6ymf6IYv7EVR4I!kaN z;60LIC=N65PO~8H>iGFUL^Wk;#&p5ZoH=PCj3ex+5J%%83=na+P#RQrrLn_0mCgIG zep#0X2vdpouBgbCHyC~FwOf4<;PUPa5=6STrSG65iAEJoIqF%ejp1X34C`bG{_&{J zmXm*p8x2f15EQZEm1O5&6;HYlMQ0i3WT%Ebobu7#enTz=H~Lu+8fAb3vjtbW00s5e z&S&q5$hxksEB!q4ig4Z)bXsRD^-cbJb;dX~ik*Up(}cCHe!li~RHZcTxnhw^?vcuE ze^+N08d$lQ*fjk=l2Nh@;`@eSt>NS5UyjyzMfCs3HjW~B! zgn~cQSMC40s9s;0;Abfob5jq=--`#g{mvKPNJ=Ya`W%K{11nZtyK7oB`Bztf-rSe{ zdN#R3m1$|7c$U@mI%h)L#R+ePQ^m&*$zD4K%>3bFyTiK19-*6=ZiZIgV>_sQ>fbn& zc3)9CD3uT4jP|ZhWdbfMbX#^@RJG>?73TE$|74KYZ`8Uiz=zKDcxAR0hY4jnlf11{ z6~AT2*(i&aB5DQI&t$!nT~hZ-UTH}l04AA|5+q^0mB3T6X?{wR7>JNV2WXp1W#9cN zKkA2d{(?9uQAl+A6R5M83d&Y7fZqPkrPjf%lW6=+xpP(7^`mkuk#tpo8x6gqd%Iy5 zX>%*QiG7@-$0UUa2_rO4WXs-|j|0}2Um>RLQD*_!>>Km30OB^l%cWHMWDLA>wS_aE zqH~_R3ixCZ3qd>L*P&rbjQ67pm(3G+DdX|iye^q^{fe=GoBnqyyz6|sa~0gwdSPrn z1}q1jF=*abzDjiy%_uYnoc8+5Zc2w?T&a`gQkJZL`(@-3R<<2?WjW}rnubM-cfV~{ zJ7uA(!S-dKSmb$924jT7XKck`^TjSvMJF3f+|$1!4pMp( z5TqK`p6kE(vXQ4T0U^Q=5Z|KBQa4)-Zj6MYt52G&x2Lf?cj*kZv~wv|4fL@NQRbB@ zj^kFh_9@J%8Urv(bnQPD*m8Srkq2A{d#hNNE``)p!327*^Zz#m1D?3yUh7X1xtVUv zOUOZ^wMVf`56VgEFCS^ln0&)%H&2!kAImd+6mz9S7%dsm?~ADN@+JRbNH1{GGU$vm zL1b?pcko4ixrdCvQ+pMK39cgzqMBTh5EIjv&i)ngL)ke8fA_jZ*F5=mV|~Xaw9NmS zM^F)#pmIe`aNHCG5tYNvxUZ0Pd#CcDqBLSCb1I;jnInV$*2CfElY7%yK^TxHF#e7! z1SG@F7}nXzBg*A4C7mIoEHB%{NKH<~hHVHeH~bT__Id7%cu<~MSy7bc zIf%!Kusf$@1II1(+oJ4*-js?Nl@AVOMFy3u!f_Lh-=W>x*KYS@gSWJnLjJSCg!O4i z^KYtBdXjK~5SH=ckN<8ToF4^Igo<=kNKWsz)RCOAekd6)lbHC9!3#>OA_138hbK%# z-TC4kC%gK*Y}9dJ(PZGBKhrUjUdd&ilqkx*Qyo($^k@eT7?^PO27O&|9#2P$OfUX( zgmP!vU;bnJC83aM@~kv26J5H&nb>Bbug6pEcZ1iOnQI(8`N6;3wiu{`KLg(>H^((f z0SC$RmO8$N>4y1PK=4COvP*#OCO_Io3t1m7zF4grt1BN({?H7HN^?Px#TPC z?*9EhbTTMn>NwWt%q%3xitA>2swz9#s{2x!#t2XQRPR;D21kGXup+;i@k!n;r@&CE z<%11aKZWCyGQj(6P#UBje<*g_uQ=^dXHN=bwITf*aAXO?+f)n`iGviv_wgf~EKX5e8f~ zAA5?N106ul*}n(4+`uN4K=3z?QoDvFpqu^-B3|J8e5S7P>SmsaTa=+($ z!}aD~U-}c^;IZ`5+7^`>I;-e>>oJf=f+mqQhlfwV8DvSWrv?}NZ~iJd$7PFj*eOw= zC&3POKj69%jP`;yjPE=~w%g`$Lo-nvgP4BN3=@X)mFz5}`E^@*q9Vf0gK(b*63hw) zy5T9n$V}&(v*qx$DTefDFw+onfVR^S-O6|F6pi1Is460D+~<+g(8K-bck)#*27~0L zeNQnXs?bOY?@VtXP~x;JVJmiE0ZAgBItP%<5AVQp1sQIDB!}odo2BPR{nVC3GC^;D zUKQB*wr+eZVWZqqV@#7^1=~0rDDWehRNeM*J|D&2t|6d#?sc+-XDi6Q4@C+dZALQg z#G(ym)d%Qqk&@ui$L&@1j4lnSseTdSa zvU~wCPnSwaCw4k`yN2IT zBSnV79VjVFIEbySMCv|k8U9w*vaPhq{~_do*4Ff(o$4itfVAb&RM)7P*^F+Hkm_-o zu0sBDq!Cw=W@4;uB%KlHwh$5<15Yivk@8}=q@YD*8V5{>4v|f}>kE89lx=2sT0Qv1 z)XCVzF75MNN03?&h$q2fME;Nsx7dVQaE_!k$NJfE@lOjvDt>N%MG|*Tx|n$)Z;k&T zBFV|y$25t!(MY$^7hRsM1Q&^*X%OY!DmI6VI{F^J-nZ?EN4mZWYz{21W5MX=u5)f% zm;f(Q?ES*tciL~7Asgk~6G z?CP&|0Q|u)yV?lt%jC^qIHfDb?th4g-x}Y z%?_`t(BtbeX~%QO$%;2`q4Qfkma}2L3tRZmH;z8-C63sZc}04=`JrK}vLNkd>DzQ0 zWI~A?mz*;6K#H2-ovkM8sfs3fTp}@%I$r*g?kVDk`X;>1+gM^iAE#BXFUEpU$+O9bR%+Bqpn?y>SThir1IrSu>+Za#iq}r z<#yAvQ*blz95tQJH$XKK7U9Kky{I*!hqCM--Nx!#%C85wZ;Ehoc-}&_#7* zCSVO8ZO87J04Z;v|LHP>b$|*?pw+&!83|uYEXtSbm;P?&Y%4#o9@gccgq0;)FiRod zGsUq{ykrs5QZxIZ_yE-nM9=rG+?1`}(fx0pf|1629^qJF!X(on%CguA? zI{@b`TtX=6g%Iui4!UO*PzBStp28NJA&-!8YmldoB#nM=aCFI5wv-rojZ%|FI{}}C z(Qn+zTtcE-=`a9!_TitvQUpuUt4+)DsD{sKtVAgtj4Sota|JP!`Xo@o%#JYQ|fhF}`C~i4E?}#Jtozy71v#2_Wj6F(2sSsG|IV`;k20GkH4$r%FPDc2^s*RO*dQ z3)Vd?j?I#PhM$$V1eMSe7q^`h6`h?VZ}s3*Fz_|OLO%RhZq43L`*?CZLrDoH1yRv# z_8QYMiY}VMTtX2FR!>?=Mj;1se9h|;X(cz$JpGE?YNx$i9aMRZots!FH%B*e zuH0vazPhW;ZhuQ!C{-ggjXRa=|?dd5MV@w^TN8(G?gS<7m--hntMV>I0oB-R#Ntnje5q>wZ zW12sW7(_P>LPDQ_HVvlbSn9@v(FR}P=_D+DfBOE$%m)$oXskIP56;n8(gfX)TdSXV z)Q0-e_vYKwVeAKAuN-cr0Hcg&2z7Lf!xeAPCmG3H*U(CEA|A52%z$RC&Y}Xo*+j5+D$SZuXTle}At6Iq0)Hj?P zj@zVPChfb%W^XewKbn1SJ6~q54xU}R9}tgy0XVMva@@(t7|}nXO0bAEUEYGC7@@}5 z5@o#xpm&Z1?(1Q}nCS6z84l#YQEBG%@M|db+cnM&wn|{8IRgeM(F9iS6*|Yotweo+ zb_Ig1Wf=1eD7kN)d}X+&gB{SPq04?6|BoqY9OaUS>S|7p%C2Jn``UfO?dVunXso3Q z!Xfcl{};KZ%+T~3*U?u5XQ;^3>Ukp^7cF_>i*# ztEDvpum(vb%Ohnzqk`v-lU?AK1zd5&PgVoG@nv}bN$0M5iKZTEeI}+e9{(XjKBdKj zbkyFkTYb%b+t1#NU|S8I5@%ABw$ENUeL@p_EgNi}r*~$LRVlF|wm^n+&d^E8`M1Kv z$WJoJq&eJO@SR2mX>VAVJ;Phj5ybgNFzQ?{H2Hz7Mm4RQF8}Za`JrZQP!;5zQ0Qf1 zTSX;fKrcFvEA)AvWjR24ME8OM@{T_{U!YWF4i=9(|4HD-+^JcK-}Ti}$Fw=7-M&4> zW`S!&?Pa>8av2NfA1EI$-ae&Yv{lj1ziYAs1kO2Nl6}PBE6(maNRA*V1354dzmNfX z4PLQixbypzmBnj&{e`d22d%}b&3Wrk-wRzd-FcCIry|`u>MWzhP2Rj5i1KrT7s_C5 zbV^06sMcmf~Ji@3@nbaKD& zF~)V3ll?ItCy7lb1Hd<=yNh`_`2RK(cj&)Zc#tZ#KhQ(||RqzUg(<(23MmKkS1J2|4A zz-Ny+JuS3UsKRCWugL<(sHN%Ozv??9`#w+Md#^h|)#D$%mz^xCX$~%?Eeu>y!9A}} zu#!|b_UobCJXANREwbRo|57RUujCe*;J$9&v)}9uN~Nkd|JKgnbYRL?#AbEsuh&%q zR= zdPR)!Ifl3SKl?~{`VZ8Dzz>bT^+G`W=cd7#AYegyCY|{H%$27So!f~M73y&W$ja5< zNBbt|;psoRuB%7H(y~{Q?~aFqFStZx-ChfPFY=MlD8ehu+{}kGD=Anr_9C9_}mZbDxdyh}o2(oEq$ z`0IR=aW>v(yrdI+#|dSS7;!!Nr|s6Dzrw8KdURNQOq`bgR~(pbr*|)zG$=7uCLT-E zJZd&bpzjL3xS5Z-RatN{nZFiap0oDoT2SP&)XxIP{y&^GQfxb0anI-U2HI63sC}0) z2xu5Q2Il|fpM+<%Wz+ELt+aFElUlF#KPiAOx4AwfzxFnZj)i{OjJMY+q_&;8Cunk3 z(^&HJuyLPYu*+Jj+FXhC@uxvmwUGPxGaala$lC|)Gx*do2Kj>Wa`L-Xk~i5FP9ArQ z-}#sLQxP5LYdmp;|N8Yxb4Q1FtmtcZ&yP*j5jC}*q93dxnQcT14(s82k`3W*JhbE# zK!Blf_?usrChT@!L&!;NM7LJ8Yoc03#g;g>QSry7>zcAF(drpm7^q4Jmu$PV!BovZ z<6$q@_P+KfRMK%?nxQVN{O`qpi!4fjm683BL=c-N2`~lSfdZ^xDSbdCc3BJiX< z@4oJqS4$63s20@stG!JAq~*hmen7nN0BwIUXkmIJkgIx+RaR71y8Er^y*?eai2kQ{ zVn;1s9u4+2g-VP;fFF9HH%WUX_j|V5b36-@>1s5+F?_>TI-T?|_IP_x6PDQd%t<_y zQZbnsB)c?(F%xeH1Zt%s0)a-u5#_fa*EAr)gHGyWh@h2-k)%80ukAheP#T*ElO>eU zk8d^LFOj;sYP&yqZEDm7fqqDj7T7`T-8zNZzW)xJXoZG7GTJdH1mW6go9_qdesxh~ zgev?l@!A`6CVSR;-nKd0;FqGINnbtcjB;C7<=mCeXlHkT9yRg2;QN7OLK~EVH{dX0 zt1ae@EaNAYcqU3`!~l%)-5P4Ez~A?^7s)W9ERF~Fw{j#Y+MwM??jmR{z}H^3U^wIF zmEwy)C(zq5Y`_>*nUf~NH0qi0GhIP0T8R)<1_>Lcl0>#rJJr`x%$*>qW%93U!8otjT*PpcP|Z@)s!8=)!2Ni_dcW`fMp_Ewgv|0@ zNNS`s+Da|rk-0vF>+P|eS?*2HiS#Fgn-mxb&k-6Cen*jYcAlx*?O>le)}biTSzWH~ ztcI~}B``m+(k*H0t-U5C2&OXuzBTi}x8_#g{(LiM|M5?MOrJK3r^N&Q9*~k!yC`v> z@3C1C`Jc4herExy{<>6P2)~1LXE^=eip55=N!U~LvMnS_4@~?fDhv(M)_3B!d$fXw)()N$V^R3@X zl>Gba-_vjwL51$;wm-|IdJ${9f)97Lk^IzzS7su0e44w#AGPOVzCa-hs{pw{Uz0@Uddaj+U4aM-U^XN5iZ9KIqSai`x*bxu8v#*XpxHrK}b9*A*? zn{(@?7}luAtSXoDhn?p_rUSC@@%<@wNn9K95fR1=gZn8P882%A7RtL) z`-gd(*&D{ap|4h;27ZDZbsje82Z7skFCuF)nU)y-1YCsuP_cM6{&<-+a_4J#a@|bI z$E#njrYlJGFn01Ptp9O+y}nQ)olkM6UiPP#cvAOZ$?Jolnj}_`93_7kTDwnPZwD(5qYhz%M__z=3c7p-oDCs9fj_$hpRa(>GPwGiddP#z>uvLuFV0lq`cx~}>kt5oo3Yg_sPhx~{MYyh zcR1N{QUi4LHqlbnA2H{^1Fzqds!1c78vhHx24PO%3)$qb zWz2LjI6dZBB1Z{Ckec4zzK`0GZ`M5)=u;hyKEbmO43CvIh$6G${`J6gO{I#9<9qHA z{ihzXJbp{@d_W^&v2he+_i!Ii|40A6oe(3*Elvq=IV1{8rIl+n7R>IN#skD%V22~1 zj46>Cw`r_(*GZB?Y6Id3_Hk-iT!r`s5);oNX74q3`%-8X1ZB6L&S29uc6EC0GWJre z0tK&+vdLhc18%?+JMv-_x>*W0O3828!lRs#P62^T)yOtQx z(o!T@h-e=X$bR7s+Q=4cdw7!b{^aPannj*RIV@rm^{ViqUtixZF{=_5<u%oFUn&Hh~ zqsk+#0zvj!1svpX^1)a?D&;S8oNhTg%!vn_s#&T=q5QAHoyUIm8P%7-nG$95&mDs% z$(qR0PaaqoS|H{9@09S0a}~My{wx}sNWdOg|KeGY2|R%CVt_Em4EZ`_RWl=2a(u2k zWIx3{E*$Vw7u;ay4r=*m`nCS^}fR<@5yet_-q?Zr{+U9(x&*(3R7*@p^Uf9O<<4&Q3ekMI) z9usDi0q=0ftG?c|_PkiVN23(S@6yeTD_62a7i_-y$U&PKKQ4)uq|Jom zTC7$DbeNea8HscnWPuaP;@5!{fIBYbAz$n4#A+^Io5hv; z(xT7`lUwNKoy(o95Q}30)g{v`GVGqjGyPNQ#f9^~4%sqmb&=_O#IRD!s35Vk>W_H# zX*46AL2V{HEAf2oliNKU9}7~C{Ovu`0AIsj2E6Q_q9d;z7{97t&?CR?!19HRd*ZIr zJ~>tWItaXzLRzr+68rZN$WwT#B-(DlX!mel*@-(|H`{ylDi~37L-$77Jz)cixESn> zs1-m#9Ni0zj$k&o8)zNi?xE<&{5HNTMhm!}U!mTw8bG0bBD)MC{pJSI2&A+1Nk-TQ z#6@;|pTQ1%z9YxP1p+3Wr_{bSBVtd}GTf&U%zHO)UPXHgm`iRMM493Wrxp*2im)zH z81DfE)c((QF`r*+Wh8Ch(2c|i$!6RT(Czq zu8=H{3x8oJ8lV5&{lSZa#t}FddcZfWr&bSxeK~8*<>Kq++eZ}xLSSa0@ z3l}=-gjPoiw}n+qDugEpgI|I*70IT2K=|vn&6RwxMt#9%(BDAZlWbk98IU+y zMUnWNX2IcX)& zc&1%-TS3dXj%80r7`df7Ha22mdfrxc^R_ZTAa;S#VPS0Yzl}h8hJ?DI;6)*$R;6(aMfz3JXc!g?S19$&8ze9y>lZ|2mof=g%}`&tnDg$b<)>M3z0ym_>d%);=fo1((=9()zr8428+H9m zc<$E)X^x&5c)IVul9ZwVML1S?js7^II2b)*35xID`$#>yRb3vCRtHyQ!U^5uleo}X zvTQnZ>dDVIy-m-z%2@o12~g`t{sV%*%6N+ouyN%$A`R+UWol9eA{OC?R@D`e6SNtj z5eyqHjRLJdgAhN`;?E)sJ?YqoAT~b0by~rA+PB%`zB*in#QAn3A?l0R2Kd!CX7QIR zPd)am`|=Z<9EsYU(Ge`(f?TrE8#=f=8J0pB7rIy_yJXOX@*S22*4xNQK!2%xxtg z9E!{SykzLH-}d^R%w+IriY>?yyFzb$gv$F~_zY?T29CzX8w#(+J^NNh7ORQt&eOpa zBSaxW4273ti#@{fHcN1p2^|A=ks)XIkND|=1)}k$W9SopPj*11y0Ylh>MwQBaG4kP zEwX%*QZ12mO!oV673_8(5Zqj>M>t!ortIm|A!0c@8qBSfXm3o+{B_Zi`#EQK!XB;p z>a3;>ShU7DE|_g01PeulY069?E)*Y{;1Bagq2`m|jDEfot`OlGAIt5ab)^p{$v7EQ zn5owf7k11m+W-F5f`iXiOYDQX*B?T0O8~fmS9nYR7|RDDJ%}ng!S=~hQ7i`yf>&`r zq=!zhUdLA)4_%Z9DO)}!fdIS^l&9^RmJa!B7TkranE0|Otpqdcpy)|0U_*W|?JuI5 zeQJ04yY*tVQ!2s;`}FZEr*G~P5~y!FgaLK_=tEKDPn{r}xRl)uWNeAsIf&G*7C#OP zHUt+Gqn^p5BCrfcBO*W>Q;7uWR}n~5HVRqyuL&00AB9NZA7CTgf5w87AX+wGBXd$kaqonyujdwJ68^5Y6nxMI|VibBFA(>?5(ta@PHR$>R&Y zN)I6NS7l$kim$ndZu*gDg#H&3k#=DkmBRQ$O%)a4ZT2%-)Db1fZ+hx>V?=*FYI_Ex zh#3ZMfs=MAE>eQoiuiuoJBB)}HTUnbftI`&A9PC_fE+9!=qte6nG4FGl?#m=s6XDL zl$YCaa10HRrd>d%amfso3ftJddoub_LPBluw%*BLtBn%y?16BWbvbSPczr6Rq`w3k zdC1n&5=#f-7utFa!pj2vGpXPu5MuslW=VaN9vC z-s-8VTR#@f{;Hu%3URwz{SJ%@0WyC$^|qy5&pX2>1(yQc8*-^}e5~z+fc*TgUK+{! zs?3(OMYu;5dh8gna3K03utKV8DcQyKl|a;LEXfD_!DH@|SR#2~LqO-=18E?tu?2;v zPokCa*ea<%dpxG`qlgQ$YA@h$Fn*#c0{-zD`S7wou$Y=5Lh4V8oRW6;XYV@vZG{T$ z;{m@J!8xsTgRt51X#O?#Dc^#cs7^E?Od*`7fGj?XnbMQj#bB(;_baDR9K0 z4){TdX2yjCM;VW`zHAY(hDPMZ?@gcOnU;l4xH#&y@ve2dY@nF=n{l z^%)KDP%G%RcyO_%!yd3!YpB3M!^E$YFMmv-{zR=^%_c^-%^NhqKRJ<(<6LqL1)|i% zK;xj)Rk#T)C{-Z%S(5W{3aLLOmw9BRiW(5mJ`etm|2jITtp&SU%poM;5v>fvsUzVZ{TGUJg4XWXNEKTVfw?lMi``4?MbNSbvo{aGNUJMl{=3= z?LjeU?l0llH!uDOM(h{z(bk~l_nAtoPtC)ae(z{w!CqKap3mttzK0UF|MEc2B$}s~ zCm(EVteE!3zv3(_BY%(jj-96UVeO8(dCmsT{m;Ro{Q$!O_ulNUs)KeWH3M3rz4e!K zu-VBgF_0j~IY=EX>H)>lZy5avB$oEiXj$jCG&;C98<(fJV$H+%lVAS3zI{CMhcLJi z*cW~!C_m%Me(GsRLa3WW&gTiHy$Vu{>B@|Z-R zpeLDv7MMu8_c3?S;V8gx=+j9=|WJ zRbr%c^vSOlVnfm#^ZTy&PAgfd*Q0&vC+Rr7?Tr~l$N*GAQ^QH*w=JPTnlL^&lU5b^ zCHv-u-O9Ucr}miy5cyFIc7Hz$5?)^L9B@~=wI*eF%&yJ&J83D#@OOm^?+srA*X{Rr zvWG3@Mv9nS9kcUnOP}_;Y6=a}Jco|YEF}r3W$uA{(m>|il75&;nt-SWG``-BXH8=8 zM0vI@bZ;a54OY@j?W>~3be)a=GL+gEiwDbg`z!yAvHneE6`l4UkEk!n4yl<8~>7${x8VM{Es)Fv2Nd($msw2>I+OrUnZw z7*t}@lW`SdOszQSjL|nEpUuChj9L_T`^pAngNB^FzgXIWp7Nz}0xXeeu$tiPhD@v| z;q+h^wPybB<);V11C+S?DkEV!AK&Pxzv^Y;uMGRTT6F(?{%B+flUW=8@6AumUi-hw znak@V3V$E;1pFEaM)`+NW`LZ-{SVoVrnlwez()aS%b19Y071C~TLwR*!U!_k*T;kE+cO|4DOxj?|g{P&w}SH+_rcxv!(puZ@wYh06FCJJY`b@P{Zdpr#MhjS!-4(%73a> zqPPGA$ex!4_q5R9B_53sExPw_ra6&T*Y_-7o?x*?aUv9uv?&W)&e*b+z zS<|SRP~F zZ59uJ&H^q1|L<(AWv=XTqzqq^Wf^~SQa<=ll+biw>qnkR2cT!koCLN4VF?7&Zh%b0 zn!vzk9eHq9zp3_W?hB`SOtpPxsqDb+TA}-xWcr5V@oV;mcwAe9)Y9R#V|fh?fUiUd zWGKUZ$u4;9MS`W~7Iu32p@i1Q@^i07gZ(|Fs?!bd z(mMQE`?gXI1Nc-&le`V{Q%$$+_aZB=1S&_}T^<`~ui-U|-|X^FN=swMyjO%#}N}zg2IA$^RDucRT|&b zbzUmwp!XK#!FBv2qoy9YL}s4hY4 z*a^PJ=e2)CD-Lp{aTBsrL5^^-j;LmAKZR z?oTYt*I6;V2<^o~=CbC^-|=Wo1CW(E#((*A6#JKjFi~oj^IhQ@P6uYxQ~uUpl6UxAZ(QpOtDT(`+_;ROwFUWFfsheObHnMXy~PMv|a{G9F4pZdg?p zu0)y1$rj0ArJ)t3%IJnK+Us@S#yaV5z45%09m_ouRQ}6;p&^f6iIE6q109NM6Lzi) zEgyZ^oUD6@?f_H1laJ$1vU$spAb+9jPDPJ}k*(|3FFzAiyd^m1E)|TDVGykss$bVd zc~|piKtuY{fpVUZdHqMF`5}M3gT6JEQ+S=zPs&j>j^}Fve+Do5bmmfO+i0X0*L{)C zY!H}^xnzlN-vT(mfw^N0U9%Bw@n}*nE#&PXZsyvHQd!?6cc3V(_@QUu?z%Gb(iG`Z zWarEr>PqOd)%|5ZIs;4~*oC;H5kCy+>$776xugWCQFN6^3(jp024>jGPLu`))!fnD zc?}{nR}QQICrW#5sRHTau;y;LTV500-v0`3Z)KxDcshdY&MjTRZ@-~);yI1rD;j$= zM1F_}d%*+%pL$S9d9<|XbAJ!J_b+ZF<-ENees+}~U~9$VC*Q1u*z=!f_+Ilex9^VA zq9<#7|1#8erE{upJ6&sLaB)_|U9C9cBxS<^bsR_I`eLq(`O2-D+X}%y3U1mh)jm%B zdj-+{h+Bi+jFeN${q=TW;jrM(eXgdTV^{1!6{89(2HevbFOQCPPXg*wIZ*ddKR(fm zi{c??t&DgFj|wgR*kT435yE2=;_K=^toY__<*EjT0pvc4aT7A0>&5zxLIc5GyQ7<5 z3@cEm98?6%-e0?SP?8*K_KD_s0XRI2Ml_BP?~^;nTfO&A7dc6ayQC@bs4ev0{qu*( z6xHcKgK)}~3#8!18}{A6rjMT}P6R@$IA>(7T}-bwzgL?W5g?L{G$LHAsIf)YPZn&( zoNs@Rq+o^*PkZ*+_D9^CZCjRtj2&Jh#&-`U1!hfwW$y8yYhOlN#KZYv?h|e9D>69z zg%)u@dH6ST1~?B)B63kbjEE`iDMUK)YlQA-!MikC=q-ug!}85yTfHoR+Q2|`drBR= z!4}g`rTVh?asbkD>kt;fWIAZNRc#+mOvC}Swb((nUkGSejLt-tQY2FRf&gW3hxWP% zdfsJQZ3ySK*x_Tyn@GQwr;PjyYO9vRX+RcU({~X>o;@_gs^mBI&e?Bj7q{+?F}-Vh zayWRDDHHS61|Yx0=>X+&JADZ+0))BHgx@cgp6@Z?_orkhPG|##M?a>eK+j(S3>ZtcC8%07 z6ks8J-KRVXIBUKsjE3SjTJwD?m@q>(t?36rF5n&(klb~Wc|`B0Gs_Bul{6^W1QstA z5O^b7Yj4|di5D&wiEd)Idn(0NI0#5W%nP9EGV{wSxyG*cgZV#qQRk|gHk8fWWR2Tx z(4&nfl}A}RNl<7Sp_dQk-^$+l7o2b50(0+Bw-!o#ddb9|#%bPhECJ>{!oh3^OV4-a zdhl{C%Lg@|JeOOg{waMC&jBN^Fuy9?sPoZ=Ke)xn$1jmi7vBrN_9bFU3&96@yUL9o zCM*h`bS;6m&XGI_Y>EUp4~51{GZnDvTgtWW)V=Lv&1sX&SppW>dmh9+Ck`KDZzL^o z;@m|*IT_l9=H|j6wo!p67em$#4EFoe@O$5cwFI)rk8$;BU=k&8$@LpGUk8a`6`)d3TCMTeG8gmmD$uCb9$Gy5DFlA?~l^Kq#A~2UcY*?3MB^I zKHFQ2dGC-uHZT$?Bn1+7=?n!OxzR>gGlRa`5{qFE9>3D=D_5zA-)C7|D`c}75{(D9 zAr6+bC*-1oE?s2k4V%w&!WiAwzJfIFV0>9i+*0I^4}lJ&#)AXZZJ;5?3kVMK~CF{{!p{+R!+M zw*}l}&?3;;<2>i5wJSGY&UdxZd|R&0!gFI>i9~_NR(rTzmRpSm|LYt}zxr&>Q z=8F07pSbbqW?q9A-hKprw)5X3)px+nzt7vf#jYYU5@Fa8!-1G>#t)QVWy+lNq`_h+ z__CzZ%o7^Of8K}XM_J*bV0MRjJ5AzwrMy5qKTHf`iAY3}H}#Di?o~iR+#Ll94U>|@ zuV?_wib>{Y#4&ZC@^(w~h`w@f&Liarf*VvxPCyIntAom(WbXe>2cq=jTPUXQEpWL# zY?lRJy$dMU$deD>A*}PnVH;)EQ)y7o z&0TtKW!}k(1?O%F#aU11kz;?@pqx%0UDYs*aQ0s@U6wRJ)Gz@M9UXDgM3LP%_v2&{ z3*H(tDG-%_-ZA_rOrFd+^7d4kgLWw1RL$GYDcj*IWo-Z`FlWoVKaQgiIKgeHO>+IdXzf1r{QvUb1XzqpoNl8~!h*73Qei|>A1!G2B z&58g-%b4yGE%6^-jWWZt()|ysCxzK9wwLL%4jNKUJ)dn{(z9q~%n%y|rG6U+>99fW z$Ur#F=}Hk+8Bc>p^(ddJsA_-v08RA}18eus8jde$t8)t6IKeMHAS65i>TeYINJyyP=Qz=oMo$RvQmioDWmw>`Iox+iz^D5TI#bJ}2#|@zmEx$0i4L(4{p;PI14_SaJo28kuAP13v2}dVda>khHlqiA?wK7faj#saDOpoXGU)I1yS}7T~66-=pyoy$bZ! zU9xXoFYMtxQj5hjORK7E#;t@5uTJuyRywXIp+IXkCsId{>wt@>iewnxlm8aFy=Zao ztI@d8fCh~?BC`Ua($T=+ng~>MIGrdGuXRZBmFlw-EUET4aL&yCf*i=$^tXEw&pnV8 zAqm?ne=^CASfSi20$g&`Ml2mq)Ku^KWO$-y#CU?+?t_g!s#Gx`QdWOnyE@23m5#^l zi2dPXC%w^R+40X?%EqIvanwlF^5_Q>y-&4;<^8D+U+g5~WMFC@{Ji{;=Lrg_W>*Wn zY|mbzjiPl9(~D%e_}}!~DiR~q1jLSpWtb`%Xlsh_4bp%fIZXiP(S_sxMNG9I{ERNx zWwwXcUVsd>^b@jlTJ5Lnp_{{yt;zluuLnNGeDIlEAbTMDS;0@9@(R2d4Ni060S}Zs zD@fsih=IZp5WpC*$aQXd(QQ3$4>xm%;&%ZTdP3fa%$uGlMi)3^u6+_rVW+r8wwEed zF*39T{HOdel6e+u#2;g>{B~{LraZay0w-qm9o*2n zDZuGw|7zo@ErUjDeuLhxXy0F#<6~V}s8O5c<@69*_7CG}3sqt_Qg0E=e>x+${OP(@ zz;0Wr#;29i^&tlKAQR-c)P+$E4(q>xk-Cpa?7n|4D}VkX_Xu_=@N-fnRN)oyQCK0nc8-+@9mh)HINvEKQ@Dee%n#5X{y7WzU>aOc`+#C=C~#vlPdZ zfGh}I)P1_HM~J;n+PBZ2I9a_9TEcF>X7tdrTkCDR|3#p3ddnrrJfPGPupgS+(Y+vq zxYZt|lX~S*k^7hn*PUO9Gfo2-|b%Jg#n$GZbN6gib5Y@xS<);SBbFTeAc`8(V`BjUGOp1X!-ry zeBmr`?6QzToGMZADai3UgoIb~1XKdCT*N9nppRnPk9|UABp#VZ6!p`>mUWn@gdi`v zy}acVF_7m2bL+=0YL;E?TzqY}vrPhA&9Y1ig*^odnYF^t-ti_k&D{Sj1Fg^<7#3)b zESbEA&?fb-719hQ9z1Jxhtfq8WU@|2_C``4S7a9-QIcUA_WvI!xiP z0TlJ0KlX0_Yi(XC3}s;H73%lL!&ZG00H6}*W1U20u(@!=q;=^AbMCLr$}bUVBfKzCigzOcuz$7 zMbMB9@-cb%{N56U656{%Pq}o2B|H3#-F^3%p5}pzKuEG+yaujSCii6~qaFv|>L*AF zWNc(@CYYxh#2N6hEBd0y%a6rPxT$T^WX*tS({mQ@&vjC4E(?KZB$QQ2vrDOzfs@?gS z|6s3n>t_+Tz#A)i)_)CZ+b$pu%DmJN#k_!0*<*%_>o6jxfS|MKK^Sc)mVUwWpTIeB zT#?%l{-K~<=x11>umN0n#xGYQ&xoerE4nob({OuQ=9s}eP7et6#ZpBudt)iUd6%Ni zC4U&?89?SdQ%AmKldfDY&Um=kFS-Qt{nPf&D=h?vR4`KqqzHX@>t@eUFNl{YGFlqn zbO2!|Z-jhwoZH?zVY3eFrj+FI% z_&4B%)A?UTU786=b^&$7$-_%{E3{jKL;H>oNuyDis2UmMYj@CH1c!TpzPbScOv}K* zyOu&xjEO$Miaho!+^GNkDH{q%<|fKIQHIW6t`aMluH@!j@bR>EJi1q{$I5BA$ ze_i|Cy3HUm#n73O;!aPw@wZ?u5fmG;hl*9SFC7m` z1F*thhd-aRJVgYiMf)dlK@y8@2qL~Ph1qBlo02~omqy}N*@!3RZ={DR;y}NjLjsdS z#AIXq)C(zVTc2C%UgEgg{2H5SbvC8KhLYU2``zAl(WbUCl|UwjP_ODSa7^`8J38)X zxGieK9=Jv0xfZ{B>xwyT2wGKo=7;Q**&q%i3UJnZH-kES;p9 zf&|z4X@Ng8zubOW8id**OumB~5qPQ>@AqH;ay0qjf!?`_O=`v8^+!jh*3yCv5bDG* zd3k%4qzt}Z6HTlpZwJ_M0Yrg^HysWK!?K|!rOlWu&Wy>c%uOlQmdzoLTht$DH`^+=O4at{QJF0 z3QxC1F=hIATO@fzcC|*&$(b{!f~4&$VTKKT5+5tL$b+oH3g{xzOo!3>Ul!aquvs4tLHde{_Y|G14JLMc z`j~fxAj(k40tmte1bbfXa{ky(Z1w7eNfdkHFUpz3)PmLYfE4>YIs{br3zPTnEL8Sp zT({%}q-$+FlH>+jGh{f4E3;^io(4A%Qal_f-!&fC=9l)l+g$ulF!ps&K!R29(=@^g4;$viy=1rREA4L&pQ)_Sz=pRueKf5vKIpzI#G3(+KQoYv+}R zoO^7RQ?C#Qtipt&ShKV%1R;a`OrF>~da0aNhN6-TeRw*15QcClLq@V7S|H{}V`68k zZ)ujOSf8ZG5uFhD8g;t_nkuqLq*D}|oAO_WxM-lkSm4wOUYa)6hCvvtp4^i_dt<*T zE1cjTWZ|fF_Dn!r(wX0?9uN>$wC}Qpv^8~4g7z-+EahSD8-44KAVo4t*(kD{fpcui zO;iW=RR;?nK;Yj$pVTM%d9DoCa&kBbl}_teSMav}W`t?cGDwB&X50-$EsKut2QLk| zeSnCHMIHxO-R^H*QhWET!~I)07<}Z{(N>V!%z3PYSEj%IYZ{cD=d84VhSu2sEtSZl zd2=m={f4US5|vrzqi+x)F2~cwg5TuAvN@IZ-DEmS&5dki)A{TUzXMKHrb1MRbo4e)qDZ-Ujws`^>>h%Li72g?}St zWN}>guD#q1EJ4TDn--#lX@?RgwC}E*CGyM|X9={+)<{mAzR3TKQPfT61fu^R(obhT2T>lb>IVRQx_v35jmP)@*)IjGvLHl5QrPa-=`L;#2)U;c}dX8Msu zJ8{ZMYFq(*{+j~us?rGy3aCTMgeN4fpJ(*I7sZhM+v4{i&)Q$H!9M(I&jVlL+Tp@| zjeV5;c%RbYDBzbAzSYJ0E-5I@F~2inATdiS=q*|@f#%c`+$HB9>7(Ur*8S(M8SqA! z5T#lZUgq>C62qTYUP@}k>am9!fFH19D1YisTe9CPQgd!{AtbqjaRXvv=lS&#szC@c z37cKY@q~yLMHwKyM399I)Ut|QvW*Az4HSnWa@avmDY++P% zQfw;B3y5yl0Y7%FA@o)1`G3`IUWH8-_EiQE`f-6yCj28D+j00Z92lIjT5xSGiyjM7A-zSFiP zs0|!F|MGDHJPBJS5lL0ASE8dxXa ze_Z_Y@a^fWdhjh711DyDQ7e@^}Q6`8SNsFsTy4EAxJQLmg zk^y|4A*dA^;xaNY)}S#Ertbyaq&p>7hf}PBe#dA|m4&_ddYh}NJiFzg>z~JmvGrR& zm8VVj!Gl4TWi;uJ!A0PgWQs=kW>4aHt-*Ls>2&}SE(m*J-)3hM-zI+qfw}_i%!l07 z?%S!RC`4Td9_SQ8O_=? zbK0}hFnT_DwqZY}jHbjmO9#z83}Tx;bX&kv7o>s0=EIXs(cgjGL*KTWvd?E@x*L}1 zApWdQ0jB}?@KY+u3W3kZ|E*D6L?v7EkzkKKA;lZtZw;}>CzaU+tpy9F0bd!ut$^Gp z?w0<^PrfUz-F-Y!q&bq`c2k70dQ!wfpDYgF!BAxKBp!?l7$cU#qe5f3V+~3lvEV^` z8Ndo$(h#inLH}xG!D^aI?pn|!TQ_x|gYOS8dHiqv7&*KE6tOSxiuW}Gi6acLoRN-Z z8lT&(c>We-=(0dlfL`SSWGH=G<>k<=Y8tg*nbTi<@vM4a0H<8Q${7bwO zVR1_(W(wS?^Ua4f1NU?1tX}4{-@pb>%E09 z?4GLBno1x)G#3`m76yEHTke3!1PFm7LN%dGs}d47sZu zXfMHfI;aBOZPk#zfV4CT=cd1B7gj6^xMb|v&j zqt_cMqT?$JhaKG~hd8p`?yXzi^cv@|co4Ow%OHLcOis&^a<#{G)&Jp|C`5eT$zN&J**XgdULX`71&!z_+1lhBDu-jb|$$f8wj*SFGYHy zO5~0*dDY!3O$SD^tK{vasb#nIoF#0Oa=0C(i1sqS5zf19p2hs|V)Tqeli1|ecD|kX zhMh?d#PxT80q!Z>q%*Qr@@&KWC*S-4U^*%S&V)wF#z;xwH5 zm6C*;YFugmee3hrp#ER=Y9FlP7O=`QTm;V@imQi{+?W7y1{BN!RHCaBenhS$!iY*R zL3dt{x)g^KxgXM%$VTxU@4Qpz{-8P$`AL4$d-MGRe z$$YCni`_}Y2DfojabVd&l20aK+$vSR;pSH7V>tpX8OfphK-e zAkYwa&U2Ri8XzIij&Vgdn;*^8Z=Oaghlz_6Io83R&|MoshWIXXOmc`m@@mTv| z{tF&!L4cyq{pe?>pbmR^cYTjg*S`p}5T43eT^1B!>LMlUUcR@T&`Gv~I$^+n_0xwE z{hIpK|9ejUtwnCuQMPt`;{Vs-IH4_y68`3I=WLVr?ud}YH`e?+L((rc?kMQi)eS#u zK!m=%Sp^w{)LXu)BLBxpWK|1z?8gTqx#edLH1^9H0KRj4uJI&9TbR?aehM`#F<^=F zzB6O72yzvsH7&xWo^tJjksN{oKOQkX89hyIJox-w@qxi#P)T;x8y3g!DI$=A&)z+r zd@oaQ7alSX0&f^nli&ljpjLZnQ20qsG0)u#>W_I5(LrgjVMhU_rzoz`FL{tEQ@qG18{N)f7D_kb4w(z#r$S>px^*54H(; zEfV#uH;?6KCCA6=*KgY_HP2^L)eXIcT4zqIw-{+A+p=f^C#P#{cC{dq2h*M6 zk=36LA3Xtl!$Fcf*?~a#Da?R?dW-N?0$(2z3W84&TPW+&(~}f460!?(OSlWLkjU17 zSXxlWQ#U(*JqRPDkU52*3A^rg+3uqCH#9LHPJDRJ?6$)cE`Uy&3T01!>QJnvT0vBOOsA8i3hOPD^FN6TZ_|pT5}BeM zO7?QzYAllc;o(E~Yz5z)#Y=G&E}B-!qqDPWYLkqh{w$D<0zTSb`K7Dx1cKne?}atK6|5;>OhOR`5yS8A+}>} zEBLaXnagQ~vxg@oX4U;}p22^M0cO`1<5{^U#tQmwEPZeW`Dn5blAr^UIM?IF6Y>>s zd(WE`Kwpw&uirEVnukbzU1Ru3!cc2)f0?zrs&_mK`?Y%J>G_09I0phW4S$EL1rrhr zKu3C1r1#b?UW@Rny&-EW%Ho}YM;6D9>+$l7QgJ_CxLt%{xAqo3B=WxvT8VI9O3S#NmIm@zo%jAjvK7UnoJsW#=CqA<+4Q_HM@g zcg>=I8|k`e2{f-fzAR=(qtslxf9WH`(Ug^Xs!VQX>-`#-T&Tk=VLNSAVq?mMQtRWJrLiGh%3pv2tN1x+B^eZo>K}y0nEDrpoD?emVgZ@nZbWudE zYvxSq6_}@N^$}a*-_CSvC^1gg)os9-?m8t-Wpp-P?@gB{jk&OCN!|0HuUGMO#Wd=) zl)D^9+I=al!1!JFAFg@Nxi-CSy3Dt%|60DKs0NT~dp(XAGfDpl>Rd`UwL2JO;6ek1Hk z8z5p^z%4}yO9eh@`Q|>$I(7)71|GT1z$Z*9V9ZafIe!OboXlkzIu68JhzeoNp$ZpkFr%Yu6p~o!y?W@tWEoJ)NV}}3I5|Z@>`MmAiMpI(&N9t;iCTjCpd}v6? zfh>iyv@~05enLrjQRLhN^iccIvn=7`_)i|hKb@yXho=AG1|&<37%S<>Q&|>L&Eb_l z+?mzW1n0?}DqmTho)!A;KOH_r!knIa1kr9^j#Byjo+N*XRmtYJ$Q$<%^HUmyXrOw< zkQA$Euo2{X^;yrU(FQgY=jk-Cu*ZLs4wH;$c5~#w8GwJqSb5w{5LBe3q1zFa*1GIH zS5<71>Xz)DLjr7QF)@*Lb$l^z?#8PO^Z?=}j6zm^(*h>6WvsZ9*{(3$OHf)XX)2m7 zzblq_lNPo4ro zAK*s+Zm@0*f9tHYqKoM8;!3VldojDN^antT#svI6ELeFmq=xXh|K)MCb-+0UjUo(9 zsW>vC4`(%)A{MLpZR8)X8qt#*Bi4scv)rX@Kt;Lk=`~bhrW)82^%NG7eNn+LTKI92 zhk06#xJad7x!^MJ^8$?&N0g&vb1r1OD8POs`rrYbs1bAFiO$d_e&c2Q5VzZ49Q(jx zGc+nZh^w{&`Sk;p&u{_f1=J`Y`>wFLG-OImWL4ew+PB4*P0y#u(Oh9&dp=4XZd2(2foF(XxX3xqs9f@knQs&zKkj z1NK3MsofZXpeIT}(qOS$ARFGJ_quvIQ~i1Qw^z8Ac!rQy?}#dW`{ct}VCA~#OkMYz z22_11H}E=@-0@q|I(rh7WKx)D3;XdMlCl(!9tkq{7sYrq!yWDwG4nDCEfSKzm%bD4 z0pIjdE1&LO=iNq%mF6nxeq>HAF1!dbHP%%CONVU!A4z8!*W~-Z{cAyYBNC%Kr9l`7 zN|yqPASkGGm((^&LK>vMAR!$pO0yA4N|)qBx|Oc&zu$d7-;=#|y*@jy&w0Gx2hy|J zg+YnhtWm!|L28Cy>iFuw0sJ-4a9zrk5Ab=XEnQA<=-z|!-GN!Fy-(-7@CEV;8ysls zaHZ3=p%$WtK~AZOOLYQ2RfEbaBDSc;L42j*YUH#aQ@Se}J8_MFxSkjt*NZ2Ghdd3` zwL9gHq+%MCJ07Cg+w_Agw7$iG%uJR!2<)|ytV|Dgtc5p~b}h(FOlm*;i2 zfqJ*h|9)}obDBBfq1(!rERkQcjow?EK84c;uidMSbBQz9#GC& zGQg~exk#>+xygW9@MbZHU}HL0h=dZ}16gT#q_g7$Nw2NCtNWUg9ba3@y`uj?hs=YK z!-WSP4B*OeAkM9SQybZ93SdUaN% z%r1Ero1h0*CvyC`4-pO91I=YnvWb&}wRw;>pcHe@$0rP*0pff6O)^WM-+{UA^#=_p z%zCEHOm{X4Y^D6ahYp_zeTC2g3qg%WcZdk9VrERqpG)$BuVOuC*be;y5zy1h7O_8F zU*g3~?jy+!tFFbFc8HSY3An2FNqk*J@{XW6$eK^P(zz2+JQ}Ye(asAMReWy+jd?o- z9CL$IK2~+t`eH6A<$7c(4UBv83hU}t3dk!;++W#recUDDG0@SzU-H(?;W^nX1A_2pB!YyQfn5O0HXU?Ai-S>I_tU>p?!?axT7Q+1T2d8-B0>dk= zrRzID{`i504IOO}4J73(0#1v~`c}eSd(hjAKUH*m26GH~!*0(!X`ZxvcAY$Yw`~u1 zW;UGtw;}D_Q`7(a;!b-j9}(gPUQ=xUqbGLUl`A_ubJy|A6HfsT!Sh>b#(d;MbgcVF z0X5UbE)}QIAa&+kO@34!1aJ9REt+c^(XH>w40t>e{ zh3II+i&XwjWr(OB8LJ*(-x*%1pN2kY#iBS3%$Ef6tJ>Ua$l}NmTvCW6*)@T)#WyY z9828`APGn6=Nt!_rxYeHGgJvmcmLfNbLCS@-=kIWA4ZftMMIT03z#zH1CU&n6b)#U zQx1_+ej{6{Fz7OG{RpS)!?7&W#KJwPD*e41+;Q@v9^=)S-2&rhbtvfCZ`GS_=W1bWz2=s20_!`IyN|gPI4@;0-YBtX}hG0IBo*&o0U+geHE` z2gW!h-zwy|oq$|twGjqfy33>T%(zSmo1%IxJM_M#7i+$2<>oO<*($v9=lVGL`0~0y z?gvBEZj{q^R4AL%s3Wkq#RXrc2OTi7YT`?jfgqAez~Y@KtT6%1+nV&1LV{dFi)5iV z(HA(+YGzW~rs$;86r(o?3qV-!I)l`13xEw};YXpM!+?Rc+fKK*V>u&Z^tG5h849da zSxPhh>b8=fH0bM*TpqRj`ZZ(gy>B!F>y>{U^qr}9(!5~V#I{}k?+-k=<_%$iDAr_X0evi?6a-Jf zEnDJNGaR+}I4MpiupgSDnCwot>j`~o{vc9&lZ;Tj`-;OJYL`ppG+vlS#F9F)rXmLx zHN0N*IYrC5jS9ZNpp=OUB(SdqwRET^-HuA`(-c~z6zUTJiWd?N4pWjDqnT`$Ng#dDD|AmF<#-JJctQd&sn);}W&I zzv=r=oQuJuMp<$el_|AfYrD76RjLZye-iY3p_{OBU3?*sA-@8XN(ajPj^H?(Bf z|I#jrSMSg8H0xLMw_#C0*zd0ug^#KD{n05xV% zh4?^mHLUeF*5_(5VC}=#T^D5B$;aSy(#=VmIupOV7PFAvfiL?tlXW=ElDLz#eSb8O z*3$x9-m>~^36XLP{I|V+)8r)G_i|r3wZ?j86oZ$^QwlYKOkAsPiRCJHt)@?n#S0LOQGw5I* z@#7#WfF09efr*EKY+#c4g*LT_z3U|dw%VT_WA7=Dj+X7q5VO3bFJb*pm1O2C(PVgcmfPDdVWJjDV$yc3k9cQV2 zC*fuL3;*gH45`{~5W5f2e?RhW*DW{FMYuDL2=cVG5XgEZ57Ip9deIOVNSH2BJHqTC zY(J=X3)~M5c`^=QNe;7bCk?2O{jA6l{l#}W<%@8?twju`8}-`=5y>e2IO4?ICtSV( ze>Ugt=lJr;ao495Uhimg3=<9?p(tvrNfPsfF~zPL79XU1rMi>U&e-!w=D4%lFBk4O*i5^B50bTGh1s{jlGe#mJtloXQ9tzlh z9Oo&^DcKZ~2@%Ys$H;dghbimrHFD4lLNtbSkv=B0)ZQ&9_QMA$a5G^TnQvw(8x~Z? z^bnl<3za&&a3PpiXLzjpb?)|*1r63r^E8lJEdB>z#0%2h=yvEhDCgXCBvFk6HdqzG zQmcM8rhrP*hWPoJG{ry^cCT_t=$9OoL`WVn&Be~C)< zKz0Gf-Z2&SIyOpnD}P_vI6bC z{fT-Y$Y$joZ&-9|fqq!wkkYe4b&){& zOwn3TMAwkARyJY@tP85P9@mxuBJ8gcrH!F>F(d#b+4WbN8JcXq5(e30WG7XW?6xGf zAD9MtZh=0njvC3B=ijGP2CTOSlRQdekmsCPP$`E(VY+Io-xeB{{}!!)-z2(Ku;`UJlj%!rejaKBvVx;GH#b;=OR6iM$YK~#T>A0hS1&02vT zh`zg~10N#fid;RcO2rLDJ9!QFOn%LLiT~k!&!^;d5k&(tkKHa;bMYIRwEUM+N3&Nu1SGg|B zgAIY|b3!=UGm|iMt5zip0cSNRbLT=BH+j)q$c{|(jSnA|043k7=O%flY5s4HiMIWd z#OCDG*z=HV8x|xqUC@#|GTWS6T1Euy4W)e3^o@O+@cH;3?Qg5c6IYRx*Z~x6g4WEN zpXqhuGOzW(n;xmQ>HUT%A>l0Z^VcWNa46haz0xM-2CWt}Se-1RAP)J>zedVI&(rl2~k(yz(i$+`BGc8!yh>{)Y* z{@1H){16*Ih7S4Z)@UAtx^NX5(`oIEA8ZEejjS0w^JIW2#8&xFB|JSFANJDNv+c=W z$2c?l0<>QBSI^avwM%=U7Pw<2%JsYhb>d5QjY0=*uq0i(=(i8FF;`v7L)Xj|rRBDJ z2hEK+A-!ipN1}C)T-5O|EbGvlri;fOwJgBh*IftuPxD^T_|oFFdyv5%wUNnA#OWac z+tlUbv21m?krvClMEIH!l@Xb0sYC8E-nU$nuoxb1ln7@WElW8s2Yk#&e$@<`eyE?& zTv(CJCve@9Ib_B@?=v!&Ey??FBdg-VN4ia(|Ff%tPJsaC07NI%f~YO#S5RLW(U<_s ziogpz*0;h8QBoEOd&muTPoTMtybNQ_NLD!De#y?X8`S~)Hx+$d7d!aGQyG*-8c35z zj1fg-DIWG43;w6})8GY|>Ft3JH8POjxE~0UU}4f(ZqudXV=(NSdH;MWnQEqJxeJUA z`}bvXj<6aQDZu^FThlvVzeUixrQ@|Xhy`T7K}Xf@(}9DZ%_2_2(swNVR+y3(4n7m@ zPv|3Ezxd(4O}d-+9^90rnPFa6LL6Ix5H)_os6PK8@e=MQWcpXS*pnqhzSwuKuT=Rw zg#r~nUHOr|wd2H=IiQf#E}tN(We990h;1Zo>)YeCk!3BofXbl?UTW#DZ)zv;dg-X^d znFMq4OLmsr{u}!O^E}Qf#L`{&>;>pk5 z?%P|+Fmc|_zr6A30eSQ$6>sdGtW4qTe#O16ZK(_n;H_RflYcV$dmKo;UpV+)L5sen zrS?NC@l#@j_JjE{w?xF=+XD2Ps?b;I1^BFjV*|6=p2dKYks4gCy?DiyQ+8oFSzm%g zJLdSy<4iQcC3^NPtH%`)jt&{o;!xH@X8c_;&J()jfjpl}7LTm(fw^csWE2}q-~kne zpUtZW`?Rl_X5TShds^^1_nlXfI>JF3%cA|D0dT75N;eR%&2Hw+CJCl?CT`$BJ-gl? zy#DQZ?vPT-q|^=&tw_D*fv@iddsV;|*1J%T9w0k8(!!Ieg-C_V9}XHs&R$TUs&XwV zVyUaQeXs?PvLK{sBP39U>}~(tWQr%Pz+wNdjf%?+#Nyg{lHj?@xYtBxAI(5^Ov#2Z z5KuslVFQt$9(&0vBkz^P8RYna^TXbk*|gY~-opnz9?Nliqy>tNuijJeuf#@D z#P(Zi{-j5Je8`o)zFBSKS+Xw}iJ}kBdt=h-b1S1Psvl%L-Vtx}b;H42{YKFIfT1X9V7uF0cz)bX_u(6k7o+LgZ+JyfPv-)qVq?G+(@Gqe$fRj-$Isgdt0($ki* z#+(AnR?>E*anFjf9BzB_7L$#B3|l_$H{HLGjJguu^r3_9=m-t}WW0R)yhSWJ^Y&B0A1UNNA9%^x;`zrNcNtP}`okeYvDTe%AtN9iM8!oFgN1 zOk=^FIUDo~J_{i{Ze<&nuW@^`X6z#mjh->6w+boVComV#56&3j%cv!$g$ox4Ua88^ z?Mh^-YuJ|0B%fnz8Th>#Sc)%1W~>{Xs0EgS>o=x2(!>&LPf7`K6Pw=kWqLr_AVyie z?}I1}!_7RpNRwRfMcHoDgW-7_XUN3)972O3U!nO)nv8}fo0u>Xao8lZZku9_>zfk0 z+F_F?A64NSs<@1kU6zz1E*h!HP^F6*-e`HX!MeTYb!0O*3jjvVo=swD0~=U!UQn9FT+wco`(e*rUU_=XL1wgBz;jX z!cULPArfE{<`fc8`*{)Ca^~8;Hq0vTj-TMD4@UAETXYU$eI=m}^K$vm&g`PmO&RePNoZSytkDB=$G$q|qG^`lKX z_<}Hh8muWqQ4qryXWnP3(zcvZZ1@^e!%3rT<8D0}vTU`l6^CNW)U1+kEXX3e*xR-5 zoPWVXD?x_+EzN=}C|f(w0py<#ITsW1HJ9ahX;MK3CEm%1t3W?4&MOg6&b@9mkdj$S z6)DC}bApV~A z1kFNC3fYsXr)TQBAvzO~O|J^)|AeGQs9uZz+>s33JRP{1_`7-Z%K9$LCsrvz>U4?Q z+fc;{Gf!ij*l=ku{A*(X*RLR0%UOrqX$xgevF5%wYJ=0A6zP*yWZaX-R8n@SX_M2v|}J-z9jtC4i^5b_)NcnZEhXu zqqr34ig21yMuy?u8nPAfc4jh)?d@BqHR|tGX5Kx%6nv8uQ?zP;KyJQiqA`W+3Y(;v z!L7-n8VrSRVQp}V8ZcUDtk6)L?V$4eF!@bq(n)Rbw2n^2Aif|K5F_p44kMpC|1>|+ zL)m=%b!P=<(2K4-olpJ&yUdm7l3JvB7xD2b^CjKJ#Z8Z;o`A5F%h;Ns4ew#CHnuDr zE-XG8@Hh%_vHH5)J6=2N*C+h+t0~)DUvI59_!wH?@DE56zIeJ_R)vdZoa|%(f`}60NB3&}%)o;%NSy36ife_#X3$idmPEtKOX9i;E$e$^#@5BI%IaSguZNe8$l zmNd-D(UuW4B_j%OfW>CxsgLB6cNAjdjn}zJI+*l6JWflw>Arc(pM@_sU{5Vz3xt&x zAZrMMu{bHcu}l+O-v2X{CfY1!;Jj0_;tp?Oq}_pFb+>tRB&7*iLMN0nCv7~z-@e;y z_9vZZqQdy{+D)sP8KkOq;Ie)`xhI0I)h_&pYVwV6aK@5 zw@@z4mY)!sx0;a5Z+p~!z;=F)P&_v7M;#FfnQ;KSy`{{LAv{GCo>)MXwI*<)AkWSD zhjF{f;%UeDw>-J}`Tcu1=l^imy-u6mXMrj&@+VJv!?tRu0fxvX*SK@=rlJ*XDcEEH z{*SniuJ`Q{;wl2oK@*Hk)Jpj;Z)4Z>aZe=Reiz#+q`{%UoVxVhg|&x{h%!gRK=CGE zf<6$0A)zjGHdDcR+6GZS&7KHRKUM0i!GzKvi-a^8;`#ArAE6}PGX9r}Sp3cgl})pw7uuJ}N; z(S1W7pFA+_DwG`Gl5Jxx(L78Lv=|0iGr9$$kz}Uv+z85l-}cc}O34%#lK0-&jy&fD zqF!}f2Ko_D+!&ZvZ}?v#Qf%#Z{Yvj8Kz-i*X(&>N%X9AZ5q`pJU04}B-E1-Gx5EH9 zAi;{_CBH3BtEEjA)p|=A-V^ir&aFw^3X>=irv9W>P?1a?`7=U2kux$b0&Fh8sLkU$ zY{gX7z$8T+woTu+S8xt>kSdoR<1> z=w_>UDxiI(z^;!8;qx{t1*_E$eJO|T$Nub9EP`MX3gUZ`^mK$r%RxLWjZ#5$_Ynmh= z>SFIIoe1A7))(Xq9QZq91IiU`y6G}3ZxicnE<5E(*n>&JI; zL-3_Zwo1rfZ>|i>?`0<%BBeA)8M2HLA{fz#7i>K-BN(nit9;5OFAl+jb*8hu$fbi& zu>X|bU~sG?T#Ga&-&5w7v$xYrEuTR<60tD4-;X~pM-4UCca_bjF8AHeA9H@^X#3$0 z>`bXaS`4X=p~gu1(Yw+Ze>$nT-6#se*x%s=R`SG}0PicOg7_|B(9oj~&$!Ac*keRH zeoCpObUSzGoP8;zj@AfVrWKKxqxjWcn`9--%Sb62YMe#Rw?{QE!ymqX^z^WiD#QY| zJVH$+9+xokGN%d0RkL5L2Z%8CtRb~10PKhpAf)8U=kcQ)A>Zd1i#}^-}Ia1ejZWCbn5)a6gk}q8b0{j0Adjsox zyD+1wG2FKbL5^}ve)viV^jxV7KFk&nv0>G*Bm#%1c{gj! z-U3fa4zGqia-kU7f*e*Z`=(QZx#6X#-)FLJY=y?kg{mkqqXXsY&k3JDW0Jj2D*pOC zYIxrnxF-1?zs5!;&3*WC(xqu6#wuZAQ_m=bTikwo(uP*NdhS^N=STXI(}6Aa z+~`XuM%WBP;UI-wO3jY3BN*8Vl6ZmH=EDE^kstKnOe-bZ!0x4lp>nk)f<^|Y3KpSU zRVJDb6_!R4>MfadG;`$+IFKNYw>KJ;S^88>BS%?+)#>Bt5#W%70}i-q8>A!~BT4@m zkOS%k)mXm;KGFbY*Rc0Z-|IQ_(=3-(pS$_;OBEGi_z=~xY63Z8_TDDFj4(qwhh2qK zv3Yu&thF!?@ssOpL9KUrS88ofxmvV2pcGL-#I#ROVsw%(m`9ptNlBMIaL-yU%T_Q8 ze`=*IKts~e{*Ya^g#mRz%3UAR7t&lCQzQ9UnS$AOHc(17;ue0LX%A(J{7< zwTz%z(!+TkjY7Sj5tGFQo0GWtm#({NzwqwS=Jb$c!F^Jx-zddu`oq~Pj)0elnM$Ni!;$*ilgiz&K?;5gF+|^$WPwqz^a?Fq( zb~@rF8TrYSGI~`>6PXZJe_22dC6XC^tbXJcDeOc_2TTQNta{%xE z<2SXs^OM`|WuV2U=?{n3{FRcB&_kvz&X`Emv0!~80i_Jz&B9kju`~wZy90=Ml)3_4 zlTYCu743;e?+V=hMGEXorE$>%0bY^gA~>Og(ek=h2Dtg5u=qqwJNMU5&H}XggBiC> z<$Rl|(XaGxC%2n;VCi4{Y>nLW8iIGqUIo`qnvax6?>8p!+p}IfIdM(!k(xmo zTwnr_!&!ORfg0SF+)qF7stCl}{v9A@XR_YV7eRi35F_3FM;6nwD7Q^z!bm5KNu%00 zp1InGigK+BJ~w%~jJE0I5@GEc zKvq8scdK@?yh)_>3IhSVgv@=bBsU~QgVtSO)lw$I>4enM7TsP9SlY7O9vRJ(B{|>q z;7L#OI|bjL=Sy(2E)6Tj1G4>XtTs=}#p@k- zA|Dccm?d7r|HVXN92d7}kXJ;m1VYCg$d#6&!^}rh=FIn|C6;WG4BB0D`c6Gd*M1*) zd<*!O%vP8J&MKu(9nl6H|6_ zC?*}pf0ept-7lCZ`$3;2=(dne)=}10-RA10ozh%i!WK-XKkS<0Aa$V1rj9hSGcO-B(aSdo;KV|MT zl-z|^Y1n*VdTT%<1FaPYMr(!@dTSi3Rpy7c{;vQM+LE76XA$Fzv8OmU%|LQ_v;_q} z0G9rKD$d7tEoMd{^E2S9Eu@)r5!ZyvYVyzG@x+BczO|jIIcpCqi3{|8anHY2{OhAN zZNL!^GB;qws_iip21(3`_5DFyw@Ju~+UF3Ra1_&xf`7c4wCLLAS~l|Kte0->`4Faz zA{0qf=6-*r(afz)?fnt~%8OGRqG@~~3-?rthreY2clm2E4~6c}C|-JN|jMknCo=7QW7@4{p*|roO!ULXk;>XxLSdqH$XH(!R zpJH*J5X+h{=avvG4&snDGby&dvsbBGY$rEx!QwUBvVX`h_a)d(cusyf@afLbM$v8g zGxuZ~%_lKO_O-i8#1>3%prgK4TEw0t8agCd%G?l}6TFfo#u|Zq(v2S!gIYgbqgaxE zF&gxZA_}awFt_(0Lk~GuI}X}xPPDWE!woeZYc4+(jt$Iqb&6Tiu`^i`54L`1jr7JFPi~HF(6e&`l`p)0FvfU3$ z`mm#yU346d5hfe`8jKL({GI_uTqkyKr}{K<=>`+R5s#(He&cIj$EngWs@sEjjkX~2L(zWWozIC z5oZp405Rh6NkA-UetD74AERquC`_D@eJJAYs6dZILEaiM*Hrf)X_B1Ix!~yR2^arV zY>Ng1x{P|lUdM{eiUHabo z(N3|4S4rL1kN6a&TB5!Ja45l9m`fZ;0216p4-pe`y_4brA0-er{7CkCePohtuQpXG z`j0NK&%^pHA`P}R?Z%~keq5ve9~K;Qgb!S++YB$SO{lm4y(RAxkCL~zz;6@r}NL-h=zrP4$q|v zwk18!lf9JyG|*C~fVeo3`rFrc2F2As25_CeM6_Hy`zi>UO>C@yI_n>lyh)re^b*cF z{l3Ayc)8phFpW;44^nX6Q{+3!o>-G1&LPmWx1^MUX*;wz%I}^dG}o$ z&^&cd_S0sfFX#d3p-+?SXc-HkiuO$s;(F6zO%%Mljjvm3<*t=z?YeBH_Ri~gn{ckd zm;B^L<*>vnEKp*KywXNx<~@&yeUghJ^~b~koTs@~(Wi1VUd~GuY;!6blwTgrdQLa` zU_SU8@Z&=m8xbZ2U}M_+vZC-K=6UWXj>C8MbnSphTEIEP8-qeKYk6Ax!YrTez6*<+ zUgnBWckLe0kOYL8U`l{@Br-U0KVlH9Ee?`p0FNy{{I9vC2tDs%p0*sCBJ%8VdFpbn zu>?+=5$>ObR5UeX`{&VvY-`QhVX>Q0))9n(RY^|&4l$@dAc~rlc--rb`d=;em;+j` zn|$iOqbrgxSI7LI!zTTooHq2DuT|e|Hn}F=P?E=zmbI$w?_~0dUPV2vbZzyt=FDOr z`7BIVVhY64M!Ho_0d{7z*`&JhO7|&7iLOJV$25HZSc5dG=yOkwwDsD=4ls z2m#|B-QhuGdES+tCdD2WLr!ySPaZVB%ua?bc+oOI^q{*gtw{DdoYNidAY1l{HuTp^ zoA1wSLmqzFMxXxKJ?KMyy>86~{w-{yx2WujXnEQ`y7|pLhYUT&#{~hMLVY*W|3RCU zXQQ6vZgd1bsCah1U260&?hio%=+}j=bxDKd=RIX73K7;r`urZdV$#%qUb`bO_e#O$ z*l*A@`?;w0;l>|~+P{048DpCVDS**o-o)$C&u9ySsv=Si=sCNz-MX(Mc_f*}Fbh1l zNgcBZ4P<{yg#YPG67r~~BHuYxbtXfi&<20_y)XsQ^wCh9&`eDS{Mp&zCZ|2QEi}04 zF^)FP5&?UW&6d`pj+^UgcqBw~&(5mCPA)AkRnb(I-%8qREBE_jz-?G+X3T$&NTB+5 zQ!S9``x}dZ4--hK7oOiCnMI_HzB=}K<`ZE`i1bYHfS9k{HqkWaJ~w}yqTrT)*i8F} zwScbBxi<_E>h$BxLZAI{*@LFwz|~E@5E2En6KYb3=@-$T&`s$w3VtU$Dh-N9eobrt zy{?-dvX+n|?Xu{cly4FxhdrOw0ba4QUbFm$##mkux;ttvTV(-%CJ+3W06d)!+aE51 zYwZIbK}WCZ*@(=5LMj$kBKMZAMksjZhQM10fay>$BP2m%r(oG0Z*#&DWAgjTm&dp} z!>do78#Kz1yt`3EB;p^{tyT2KZKR*Sk&8tRpqIL7h0*s^Ak{|Y=2H4QC+!nbO*dEEU7MHW{ao^S*R)5Gol6aXEaV}4X3*iT4%i)(-V zS$Y67><0tN@^*T9(j@Tg^rPMq_-CsBzEgQJf`%1aWP#}@r_JEGdiBPEku`kt=-p&O zUA-K|iUpBw)lv&l&;tqI*0}(zdV6UPuw?(@GV}%}l2_~fJp}!es@rF>h}r+m08O>U z68=!byd7tpep$6lR)wp*FQo*JDfnY~v*)mO4{unvIV!<=MiVm*77|mxgDqZ`Ss?fC z(%{>Cn?TvNyO&lf2ny{)k9cH3__x^m*(juE5dTySA%(qzsrX(dp!r*$qKHYBmBAOR zBXBmalhhm+ALA=s8?Gb{oPaS^!8#Q1IHWq)u_IB4>H`*^&-dX!C`EsIiXu>Fz66H^ z=3tyCGPI4ikh{IM^Y|?rMU*O{31^UcHG}Ocn~Mw2b4;!RBd-{>7UYNJ2BUG76-x-V ze|5M`MAgdROqBhwp_Gyx;rzCKZU5onbx3ed7VW>J$S6Nofgbue_QNwbDZaMhUnIe( z!uFfR#`&~APgBSJ*2Xe|YyYsH1y3BqheZJbgk|td2T3fqXZ6bqugEEQE4;pW?!w6cLB_H*X(9bp9gZpRbKRBWnwxD*75uS z@aF#tk!DPdLXp>qRStK0PZC3T zI(gqYvF8m)kq1K$4qC7fIzAY<`gno+np>-%_@6TBK|Ix8eF(Ny-?(^@{=-o!bfx zA5+iwn9r|@Ewe#Ms0AoZ+ZS9k+W+lB8!h5z_dlFpik#=6C!M5s%g9f2O3@=FaVnJZ z;d7^I9i>$vgnh!@5hrN07U;epM(M{Zc2$ahFOzhkb;n*!To$MXw_su1k(oJDu6Y%vUg&x6zL#=%xy!rh{ZffstJF$4=-^o7_ zt}l&yyhmu0wAsqDUQ(J75_&+{%;Z#?LOTr_)j=(WZM_*Z#e4KmpEPDqmvN0+KfVxj zDBSRRos=Z?+PgQf2Gb72oqkzgmu3VNW&k#&C`D~4hj%=L?j-#ioVH=2(;8jX@7WRV(G;K~803`U!5VI!CDpnl(; zQNDbVfi7A4n5JL5_(c}guWmF}_c{<3CQwPPBdC{eyO)}nm`?}RCBYVShr^o?6Zuh> zTy=L>ES7s!*z8b!76R9^TN_EFUs@dH$T@`u1 zQfJh%yvXNv@_prT3@tIfJV=wN-3-i#O;ZkQNczg~V`vZ?poOVyT z@B|$I9YlFtv}tSbE@K3>wt7qZbFI9hD_r0V)9nAEBFJHhaiDR&C^+ z#1Co!VZha`dGN02i-NuRk)U_k|A8M-vI>xP&I&5`-(IuRGO?Bn%)ierR8EqLojdzh z*XV$uE6X{f6ym&z%#ga4t_!LVsSA4Bt*`n-KU%_!)0-~g`P|vKtNLG7thBI{YYq|| zFfNgi1Ky$@$M|x(vV-Ssyht?kpt#fS2a{*&l_r_$-o2Xo)2`+C0b{O*9(lNg)*z$I z(9Qw~V@_`La#&4YfuzkAi93Q0quTUL`EKIic={Hhog;9jtHr7N_GGBt%QlO{cAD)R z!SO@R)i)Kf4~sI>dBmaDJ{u&&-fVLlL0}UzWTRve@1712DGj}TTa6>cL4R>s;HP{= zN`9JeI&(e%moTZz-+*{f6Hu!%CEPi*x;UfbMIIpDr*I{E)#3|^BgUq}&HFwe^ufpE z1hL|I6-_&D%j9jQ&!#S=%-t=4GPlSt&BUeLI5j&9z-^Pf$Y3g@oG-%=wXl}1F0coS z5ir#iw6BB2kmmW-IqhG5*xCL}F=GwM<%YeoytK5ntsv}b8VW};{JiETcdZhnNG2Cg zaLs2UYmHaul-M6igY>vYbietG(cHDVj8L3Ax3)?7}s2<8efC(}XKwA+YY zY5yrwKbRM*WAcL@U+3jm5L14oAlT#u61eG*A3oq~Z^RE(OcX>)fL;3si^*9xrLjIe$ne%Qt@F^FAe=lCu!_9PY#mWJC}A7)n+vHP{326XQ1HY~6&m`avZEj5ToawpCN&jh5VXTq8g3HVRJ~b4CTZSyg*%NArf;@Q3FW zwd)h~%(vfNE$dedN-lk3oOvh(h$I&#f>oIy^pcQweR-f4%xz=AgrO5G^hRQIncxJq<+9iGV#xvw|!;mSdXq1Ngs-g4MxY;)jlxu6i`3jzb~%Ux_~3U zFPfY?6r3-ZlSFCYoFEXE_L#)yg~qT@3@U~Ac!qkd=%q7I?Im$!A|p`9@(Q+v7a2^#YJ9>(|5L4)y3 zsK?k1vaOq+8h-wA_p}4M{95Nt=%saS1lC`K$U6HOpt||>CGyLAyx+(J?WbfI)l5L; zD9M5v(_!`m7JzP+DlxIRW+RiWw?t0JPg3b(!Zn_rmbslHVmp_wCtQkjzkV|XRx5?p zynJ}j)>LN(1$VT-IemaDg(*szdM7>uQtk|(13uU7k3EVpvcAK+h4j|V8})2v zVWFcHY^R0@=_XH~uwB-{IPSV|*dAo6J8z7~;9avfSUQ|}q<)AVK`Z_`Kbvxe!P=G- zRJS233u-PeFE{v&i?r#%?&_D=eF87kGB@u>P$%?V^z-ZdQ@B zjHF4XYnUu4J61|~wB$oV=q?YWqW~Zni>}}~#gF$ts~^QyrN7y!%C$%3ge%6|*whcZ zx-NTltAPFeS#xtKVWX1g)b^)man+G`=)$q|<&V?@K3m^-*X|UmFLMaP5oK1B$IsW3 z7JmQtH}x`CAAbz;H(+Z~9@8EJ+r$V9wEna(6B`ViDH9k9`Qs64v{I$8u76u1O$bfmaAc5@HRNM02*m3qK+Z#!jUj-+ph^d3946*9#npeMS zaGiE#Bw0EP-kEo$9tcI#gPe)-00n2h9#q(8!$B=>tKTE#&eXy{?&&|L|J{`JM0_bB zIli8t-D4QhhPJ#zc=LgF^jdPJJsXej%#Nd9ZeEl8xm)l{Cpm3>gL{p>Co_iDB*PZm zLE3D}Z+97Rc|Gl?fSEWe0gUe98%`wUNmg=52@7QgEIZ^3jLieKl4XG-N62pED-8yV z{?lo9pS{4F5`D|-@yY^qQ$Of{CjcW)ptm5 z2h=ll&P~vQmle{26nl(}XUkf1^z6R**gh}_O~srrW6t;`fhIh`Y}YQ^`#l=(cELro zQ~rj#E+%K;Y<8A0c_Ynh^T(WD#9iwi>-DV;92EQgem*PfW^yZB|xYr-!!>*_p zXbpvBBAz%XBiHfVa&TS%Snv-Py08x-#kwVEqM0C{-BIBZ00TINUQ4jHkt+K6JPAqX zZ^rXIpJcr4`V{)jO@UB5UQ}a~SP9XTghJocwtOKHW^zA?1%`-KSwmd>*Cgq{(ZjOiJCSO8UISl?a(#~eG$wd#$0}@eKfA1-eg@l zg+6(aC7Mz@$D|-Yey&@~S5JX)N=Hg_IDC)Rqrxi_gj^|6PgKG8>9FsLt61O?_|HOy zNFsbP?->JI2{Bg9{Axls>4*#yS*Rt#BCidfyxBXO;o(N6BSpEjs;=b>t0O{XF~ayv zy6d`-v`V*Tu9$^uG;pp)4x}KH!J{pAEcHb}pY!L}d4Rtj(`4r&!$%}jt@{L-zAsOx z6=dQcyoDnLNPHYQfczt!aV$p`?u+D3^i&gEZrm>3x$e{gn_)wTbMZHj!LP88!3Xj$ z7`WoPR=qy!el-Vk8=4Fj4ln94MG^H&H4y@UTM=qwAghfek5)FEt3pJfTQLY@M{~wv z%DgG&qx(3`hbS^bg_(q!?rdx57KIxUq$<|8Ap$=1IkXDo@W1-9N=zCa)>E8$0L@yz zad~<$0?-f(3j)WcD67AFL0f#1O6aladUh#F(Dm^_nHxgsHHLjOehgy2a-<0kh$W?5 z0FtHV7+L`m{}ag*BFx#|-r2Ly9kK%m73=fmO#G+5 zCnX=kT7II!G>(~xjCtT#kaBNYWadIAo2No0@4-OnyhSij z>sBC_06#1n+UyeH#0MSuNwgYD7NJiuC2aR$zQZlDR4?U8D{@z#QS13hENCzd#SCJeiMIk8>JeK_rD zSsH5$xOqV!3kvGf9}8#Sw1)-gAqFtF>|w)Fqz5h*QIQ!tBVoO?WwD{YqzIqUU&t1X;&=2art+rx)&vCE2=JJ!zmpYJKF>L>Y#U z1_Ri8egG40%mt~YFo7kFNTyCE1rfczd@Mq<_Xph9UdN$+l&|vM`NX4FMQ!X$Q{0!$ zqj{w?m{lB^5mNWk&P=dSqGm;j1H~wfRokZ3#F!Hg$@~yOD*Z5_0&MpFIAUJ05_zTF zN}$HbCyLb{C{^$PG;0Vy4mzkcbDtbd5giCd@mK-7gujk|??I?wxl#GTmG-xN136HO zyL))A6p)}>1u32cjrjTG#!s?xHh^Z8=IyAl6W==bLZuT%O*hob9ZX2^_pz_tjWXX#qw`a2m>f zsCu3(K`x(1qp8t0-g}DHPP!G#M${~Vd|>;{7u`y6^AOWn6=pzMC<6@OKVr}y=f>ed zxx66Xe+T4rG##^_OJk+W6_~r6&_IZ&IZ@MIGmVfrF@cr;KaS4B5z7C8=X&Yk;w-sAQD zddF8#Ac9svaRQyO93g^qe=y?kYTvn*7~b_StmWKt>1OzC!l}n;T&H>X^V1D`eiizV z>I*biIQTK~V@~JLI+QkD1GiD6PnoqCJgtFYAdXb~8~2Ja@MByDxc?W#i(?9Zp>4M2 zS0Wnd%YCuhM;Cv`yV3TXQQIrVS+*F!(7|-eqTs^0g2>~MT=J8ex$%4CHunR-fwy(Y zONsVAw&qTg<2fdmn}tQcux+U^uk0Z+{avTuO6_&5=!lJa#Y+yulgdh(vAkn{|Beej zgxzDstYg;Bn5Mpa*MqW4;vBxSdIpinVTto~pXTCPB{Lm`KohZF?DoBrxhSXqx|N21 z7ied4!fk>hfs&90_G+(;o|l_c8R_g>MLNie1oV*={`A(Y1Hp@rnC^uLi67TNfXaON z6*749(&TSA;E(4|RJ2gqDMT8xq<|ZtXX$_h8$wnnU;Zh$)d|nEpHgkh)Jkh6x;ABq zx+!R(wbOlfWI!$YM`PMUA8yzH?gcFnDSwCOS`<7~@Qu5a4<(pNOqaFq)TGV8>CSDU z1;csYlTWH&Wq!0wx>q24c+?axm1en$ZA--7dAoSu>qtym)M6OP1_ z1@8Gim}lV_aAn+3R^ZdHOMQ&}y_K^2ppKaRhc3!)^B`=knxT9F8@8X2x6;?FMj744 z!erc9pOnLu0A-?TRk~5>jo^=EZiTQR?w6{&nHSM@uv>FIWuV3@;Y}glxUP#Nh-%AY zm{MQ11AI4?l{hh^$~a-AVfG{ci5QTvY$ihycnBr-$={1ZEW7g*9y|nRhahL*{i*Pc z5Qn|)Tg6!IxzKOQ)b6=2-((2F!f$iii(zvnq#%-IkN=Z1<(EEb#7|S`+fF(s_7hyG#DFNNi75i8b~TXJK=Gk7oTGQJ6|#`01-^TQ|1SJdu~_}yI4jePm# z2wHsqttIC)vXUh$Tn*~7n-4!R5yolK)Io^YYi*3Ievn_s!?Xn#TWOve(;Ztx&iEFd z<5dZJjyRFtUNMZbI>io`JYGp|uEF{p$b!s!5d2m2MY&JU&&{dux-mB&0^zSh1i>=xoc-syAu@(>n0=F-s!ug3u%8$`ws&4~ZJkVgM|sH!{x9E~uh| zt=PJ$z)eagC3M7gpz6<>hradaBAyb(R9-tS<>UHkEvy`nnAb{@rZRYmbv$zCopTfk zRKo%Z?l;$SDZ!%!xQGb-gA0R@nH(7Bg3`GrSAapXn#RtlI*08MxN3TN;jm~qt*hnaQigf{pDoQZ=(($%)p&jzf zNE$Y_eQIWMO6h3bpq<7L$1_N$hcxwAp+fyQdHJBq)2;s&%23S(5m@cjweHIdy&@`1 z8zm7na#a!7r!E*lh&E2!gz>(m)>wgbp!QD+6*2fVWV=C43DC_uvl=Ff@OHYr^Flu1 ztTSGaCIoBp6cHjTwkDnOGH$%2sNn)i#r^ca^ScgOm*k#qAGjeEi-d1$%sg#8f1zvk ztKLQ6J3tHtTKZQC^Ip*UkLz{+LOXj&E=~|~q46Qap>-LC?JLW`))ya$g&X^%_lHdL ziyL+=mo6XHT6{R0w`3vs6HsaraGs_+P7 z^Fa&DK%I0ecRZI zMNS5ew1?P;W-%PBi~t4oxKe%y~e33da&Qq9wcu z5ytax$wLFUD_YGDfosMSaV3A!82&BE0CkQ)xNt(0(huDOXUW%xth_Rj4ZwfbW`_YA{B^_&{eq& zWA;ks$kJ+t)SE#*K>0(P4xNk)f3r8pM_bl}`EBO#0$?bEVbgCct+4s6Csx}%=)-cSe)BXAH(Tg%G$14aH24p7wb|>roZIj?sI{Q_l@nm!`2)>`0ZONBx=~>g87+-IsTS+RnXV zwxWA*gG6Ih`+Ecp#-tZVj*EB6f@%KY7NW!T~?rNKDOi)lnoy$po78TN#~ve1}vSNmXw{eklr z3f1!Bqs;&&RR~t>IES=G4kYakbyht=10MC1ojRc>z=n%ap7gqkYcb%&&6xp%FZbKF zZypVuJ=}87sJo_cvW1KP3jdVRgt55(f~#!VY$7Z}oJUWPTZ#AZRTMtvZTY&5KCCZk3j>O6HrfQ6$%T$lXR0lLGLNPxIf zl@!P`8Eyn3-?9+5BxQwlD%YI06G35Dx@mtvqZ7zQ0KeDfW9r@rHwvKssOG%Xjj(q* zrEOrLKeeUVC}7%1XNx5(}A8VZXb6OwtDVd-n+)4omHbJ2%Ik05WK zvgljoo}p+EOh_X+Jq~f$e-SIRlnrsnj6)}&5ttbpJtBpRa)*Q}%qtcmul@9ZTJ^wt zYWK5Kryc>LbF>&amEQpUNocT}>*MWiCQq>!9J(b^uuW~Va@3pJV~HJHW@eE<(B%9k z!`ZkS^fl9F;7idf01hevsMmW?!*+culdd5Z!sNl~;{()Wj-&ft#$0g>51;hm2Ae0o z&*RgURNwQc!ciaAOPG#+>k^|8wIMpHAkVq`yDQx}3r^udd9}f@O8@0#IEdkdI@{T_ zLfuP8D?xQd5@5BZxxGU&6A89$O=qykf+ivGr&mbKFW+svO{hCwNrf=Jgit-O5XM?C zKM7_^oTohmcRO+@0-E?~3p?`F7oRPQ?Zq9rQ+gg+-6=3ZUp+3F${l{aOsQeH^1CZ| z=Q+DPdR+c68*ulH?cK<9KPSTB^)ir8i1oFWD(9jSZScomXHk{k3wLUlu(%3CG>Wuh zr*qnQe(u<%=^x>n%IfHTuRw!3XY*{mERz`c)({adjHYgv0!U9}HuKH;1LhdC)nT8% zSSi8X0CjLh`*HgiOQvII%UMzgax<>e7#YwlOA{VtwNwVrBhlL8gqQpkPU;gw^`nqS zu7-$y%M1i?$N~=uzyFo>y1;*KpAnz54Q?d`$4SoX2jT>XuBog*WycQc5j`MEbc5P+ z#pz^F=f<$N%Q8RfZ8J3NcYn#EprVK9Cern5eE)Q2T!yqohwvzWq66FfpB$84MI)g- zaOR(OR|>K1YaXOjkHB|bF9p=qFk&nwl(mDgfpy)-01A$+Tfsp;h^q6OJ!J^9hnu=U z8m%h}MYjA}Izj;mmU@1ut6;7Od` zk8T?5sTM{T)E)ZB0A}#Em|@s*Pgja*T#Nu4Say|I@eopx7vB~^PNC}HDEC5g2@63| zuvJ&VqJTGRAD-1*7Glx@u$nM!%hztc;?3IRaRVwaEKh-{*!*=7f-`I>2iMUpK1Xpl zWtkt2(Usf3T)CyyeD%ZLsb>9g+mLM`W4t6rE68dn0G!rCteVjbYB|0;e!v)fLPLVHN8K`rYSCJ)$Bi^wZnLTPMQn1=}&)OEsy}Lmb zs@^c0L#j0=-oD8J6#lin-em*iU>0%K`(PIOiWw9W&pOCtKtLHW2e4dWha!t8EJY7jf%h^%Rb3I?5)1rEfxo;7r!VDv z;2t%$N5v-OT2ua(RW+szJj7D|{0?%zydFSWN1UA9Ho;d~Bp2Z}Zwuv+bb=)cFubJ< zFrl~4Zmg_z2grK9p8vq|eeF8sZ)q71X@R<(iN)?21A!eQ$>XsaV~iT-pW>Qb2%8W# z*Z^bYwdV7g&$zHvT+fyiPv>DT(Mh{dIyyx6D|%h%vtl}4m3ziaA8(*T7#Yb|W`Q5V zXI`F^Da1WTwE|=}U%V_6>%hiY;w68undu$^T`Ad+-IR&IWg}xyKy(JL#`Obd7MJ_; zjqUrR!`{qAf*`h%#wOjB7tVY;OjEVd#PF7%4E8q88YjyY+V=PNM-$ZW&snO>+xvl> z<6ZS&>$rHJ07ZK1>4pfo9)HMfLQ`q~hLaCj$_(x7aQHO#Q;TV&+`z4>WI4uK0Q9(f z)P9^+^y7^!Q8o!z@4q* zwDG>At^n9T&{Z}XK@mE;>O@5w#*c2Er@}2%TIRpExmMo6^nZ&FvJu`pO81KIDU+4K zh(WxcmzXh-WtHUU8oZ6Es`IK>f#^+970G?tPoZwtTEcP}==-!LT(omw)niHL49Ag7 z#zwK}Q)g&7YZ}!0lgRN3qp#{6WVH$j9D-x%gv>GNb_y)i8(Q9^oQzMUe9}{?w?= zL+I}&?rn?JA$tifgz6Y|#I-5a3|1n{Z3OM_jLN%u-M8+vlsXR%<4q!m$QtfvB5JIXY*eo`izE!c^ z-oX`zKfsWtGKS|Np}whxXPXgE4CoOI1%Sg=8N$!w;m@0liGf@M=Px3rH8F=pzfLtp zaXcYt`WYF{0=71#(^@jnc7WdM-D3=l@0MV5V&*&kjjGGA!m_xEe)0kDs^Al}19snj zUk(!_WTxhJs~P=Z1?MR^KarVxN1Z`gK7a0A(RDu01_(&3y7C3~@Z}ySZE0V;61?eq z$At3dTT|o@lrRIPTBji-0!x3g-ReN(7i-dnppk40rW(Qtt+1U?ZFr2C08!UO=}&jTk#&>+ zbvA5`r9qAv_p6+r|I&*>gG>J3B93w0wnz3if1Um~zzD5Nq5LFz<{$VNemcVm-t+=8 z2jr<0&JVatzPOtZc3WgqI5l+Ct%&QclU2FIlX`%I-!&I#IEOqjuRmy&ZxL*MJNWC^ zgEDXB?!4U+K`A1Qe%vXUb}aja2G69VM&)b45Xdr617` zR_mE@LW4h}2fDY^dut;|@hCgsrkBHxo3kc$vyvZEbWqF`uOW}lkXt4QCTK8igxG^I z7oZrGUO{M(2N1NEUKm0$SpBDaFncUK`ki9^kMhXXHDj5$3()pA$+SPXsqs#UL1a6V z8VjAI&n|*9`!R<7neNW>KWCu>d3_2U+9I0j`L|~V4442$uov_9gOU^1fT~XQmjXCf z{!J_iJ6}?G+WK>Ic|whvq7_>!*FIVJdy_#F)j9^u7)X}pRK!>?6Ju_Yi@JnNVOC)4 zmC%AM#h9}mDZkL6_!Ogf&!5!wl~9%6w1F!?;V5+>4UlH}V@8LD6aMb7Xe`j-1k*+U zVA8ycvUuS`?T}_RzCahB>68Tx$tT>rj6Ay)U_j9@!ocG<)hY_Res-4}?Jz}bucpwC ziLhnG#}wZPWX`U=7sc$PQ-3U7A^vN%E()HNHwEkcHyq@>PrC∓t$dRJGIadE?vc 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 Kx8Vjl{{H|h@<;Lj literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/icon.ico b/crates/borders-desktop/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3636e4b22ba65db9061cd60a77b02c92022dfd6 GIT binary patch literal 86642 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 AKmY&$ literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/icon.png b/crates/borders-desktop/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cd2619e0b5ec089cbba5ec7b03ddf2b1dfceb6 GIT binary patch literal 14183 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